mirror of
https://github.com/DeveloLongScript/MHSF.git
synced 2026-05-09 06:24:59 -05:00
feat: new documentation!
This commit is contained in:
parent
c7a52700fe
commit
60e0c5863a
5
.gitignore
vendored
5
.gitignore
vendored
@ -7,6 +7,9 @@
|
|||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.turbo
|
.turbo
|
||||||
|
|
||||||
|
# contentlayer
|
||||||
|
.contentlayer
|
||||||
|
|
||||||
# cron
|
# cron
|
||||||
/cron/dist
|
/cron/dist
|
||||||
/cron/node_modules
|
/cron/node_modules
|
||||||
@ -41,3 +44,5 @@ yarn-error.log*
|
|||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
css-obfuscator
|
css-obfuscator
|
||||||
|
|
||||||
|
*.sync-conflict*
|
||||||
42
contentlayer.config.js
Executable file
42
contentlayer.config.js
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
import { defineDocumentType, makeSource } from "contentlayer/source-files";
|
||||||
|
import rehypeSlug from "rehype-slug";
|
||||||
|
import GithubSlugger from "github-slugger"
|
||||||
|
|
||||||
|
export const Docs = defineDocumentType(() => ({
|
||||||
|
name: "Docs",
|
||||||
|
filePathPattern: `**/*.mdx`,
|
||||||
|
contentType: "mdx",
|
||||||
|
fields: {
|
||||||
|
title: {
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computedFields: {
|
||||||
|
url: {
|
||||||
|
type: "string",
|
||||||
|
resolve: (docs) => `/docs/${docs._raw.flattenedPath}`,
|
||||||
|
},
|
||||||
|
toc: {
|
||||||
|
type: "json",
|
||||||
|
resolve: async (doc) => {
|
||||||
|
const headingsRegex = /\n(?<flag>#{1,6})\s+(?<content>.+)/g;
|
||||||
|
const slugger = new GithubSlugger()
|
||||||
|
const headings = Array.from(doc.body.raw.matchAll(headingsRegex)).map(
|
||||||
|
({ groups }) => {
|
||||||
|
const flag = groups?.flag;
|
||||||
|
const content = groups?.content;
|
||||||
|
return {
|
||||||
|
level: flag.length,
|
||||||
|
text: content,
|
||||||
|
slug: content ? slugger.slug(content) : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return headings;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default makeSource({ contentDirPath: "docs", documentTypes: [Docs], mdx: {rehypePlugins: [rehypeSlug]} });
|
||||||
93
docs/advanced/external.mdx
Normal file
93
docs/advanced/external.mdx
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
---
|
||||||
|
title: "Troubleshooting: Making external servers on Minehut"
|
||||||
|
---
|
||||||
|
|
||||||
|
# External Servers on Minehut
|
||||||
|
I think creating external servers on Minehut is a 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.
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<Separator/>
|
||||||
|
_Note: This is an unoffical guide, but the offical way of connecting! This is safe!_
|
||||||
|
## Getting started
|
||||||
|
You must pick a host that allows the following when making external servers:
|
||||||
|
- **Editing server flags** Editing flags for the server to run is essential. There are many cases where you were not able to continue because your provider was resetting your flags back
|
||||||
|
- **A supported server software** For standalone servers, you must run on [Paper](https://papermc.io/software/paper). For proxy networks, you can use [Velocity](https://velocitypowered.com/), [Waterfall](https://papermc.io/software/waterfall) & [Lilypad](https://www.lilypadmc.org/). **BungeeCord is not supported by any means!**
|
||||||
|
|
||||||
|
Minehut offically recommends Velocity, which you can find instructions [here](https://docs.papermc.io/velocity/getting-started) to get going! If you do not wish to use a proxy, using Paper is recommended, which you can find a guide [here](https://docs.papermc.io/paper/getting-started).
|
||||||
|
Before doing below, **make sure your proxy _actually works!_**
|
||||||
|
|
||||||
|
## Changing flags
|
||||||
|
To ensure that Minehut can properly connect your players to your server, you need to add flags when booting up your server. These are commonly in `start.bat` or `start.sh` for Linux-based hosts. **Players cannot join your server from Minehut without adding these flags!**
|
||||||
|
|
||||||
|
|
||||||
|
### Velocity
|
||||||
|
Add the following `sessionserver` flag to your start script:
|
||||||
|
```
|
||||||
|
-Dmojang.sessionserver=https://api.minehut.com/mitm/proxy/session/minecraft/hasJoined
|
||||||
|
```
|
||||||
|
All flags put together should look like the following:
|
||||||
|
```
|
||||||
|
java -Dmojang.sessionserver=https://api.minehut.com/mitm/proxy/session/minecraft/hasJoined -jar velocity.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
### Paper - standalone
|
||||||
|
Like said above, if you run a proxy, add the flags for Velocity. **Adding both the Velocity (or any other proxy server) & Paper flags will cause your server to be unauthenticatable!** <br/>
|
||||||
|
Add the following `auth.host`, `account.host`, `services.host` & `session.host` flags:
|
||||||
|
```
|
||||||
|
-Dminecraft.api.auth.host=https://authserver.mojang.com/
|
||||||
|
-Dminecraft.api.account.host=https://api.mojang.com/
|
||||||
|
-Dminecraft.api.services.host=https://api.minecraftservices.com/
|
||||||
|
-Dminecraft.api.session.host=https://api.minehut.com/mitm/proxy
|
||||||
|
```
|
||||||
|
All the script together
|
||||||
|
```
|
||||||
|
java -Dminecraft.api.auth.host=https://authserver.mojang.com/ -Dminecraft.api.account.host=https://api.mojang.com/ -Dminecraft.api.services.host=https://api.minecraftservices.com/ -Dminecraft.api.session.host=https://api.minehut.com/mitm/proxy -jar paper.jar
|
||||||
|
```
|
||||||
|
**Along with this,** make sure to set the `enforce-secure-profile` flag in the `server.properties` file to `false`.
|
||||||
|
```
|
||||||
|
enable-status=true
|
||||||
|
# Set this to false!
|
||||||
|
enforce-secure-profile=false
|
||||||
|
enforce-whitelist=false
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Lilypad
|
||||||
|
Set the following environment variable `LILYPAD_MOJANG_SESSIONSERVER_URL` to `https://api.minehut.com/mitm/proxy/session/minecraft/hasJoined`. Environment variables are set as a seperate command in the start script:
|
||||||
|
```
|
||||||
|
LILYPAD_MOJANG_SESSIONSERVER_URL="https://api.minehut.com/mitm/proxy/session/minecraft/hasJoined"
|
||||||
|
```
|
||||||
|
If above doesn't work, try this:
|
||||||
|
```
|
||||||
|
export LILYPAD_MOJANG_SESSIONSERVER_URL="https://api.minehut.com/mitm/proxy/session/minecraft/hasJoined"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Enable Proxy Protocol
|
||||||
|
_Note: Skip this step if you're using [TCPShield](https://tcpshield.com/) for DDoS protection._
|
||||||
|
|
||||||
|
Enable proxy protocol in your proxy's configuration file:
|
||||||
|
### Velocity
|
||||||
|
|
||||||
|
In velocity.toml under advanced, set
|
||||||
|
```
|
||||||
|
haproxy-protocol = true
|
||||||
|
```
|
||||||
|
### Waterfall
|
||||||
|
|
||||||
|
In config.yml under listeners, set
|
||||||
|
```
|
||||||
|
proxy_protocol: true
|
||||||
|
```
|
||||||
|
### Paper
|
||||||
|
|
||||||
|
In config/paper-global.yml under proxies, set
|
||||||
|
```
|
||||||
|
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)!
|
||||||
|
|
||||||
|
## 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.
|
||||||
@ -3,3 +3,23 @@ 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.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
Make sure you've also [owned your server](/docs/guides/owning-a-server).
|
||||||
|
|
||||||
|
## Customization Types
|
||||||
|
### Discord Server
|
||||||
|
Enable the server widget in your Discord server settings, and copy and paste in the Discord server ID, and your Discord server will appear!
|
||||||
|
|
||||||
|
### Banner
|
||||||
|
Your server must have an image from [Imgur](https://imgur.com), and can be any image type that can be rendered on the web. Copy and paste the link (not the link after uploading the image, but by right clicking and hitting "Copy Image Address") into the input box!
|
||||||
|
|
||||||
|
### Color Scheme
|
||||||
|
You can pick any color in the box and choosing a color scheme to show on your server specificly.
|
||||||
|
|
||||||
|
### Description
|
||||||
|
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)
|
||||||
@ -8,9 +8,9 @@ Owning a server is quite simple and allows you to [customize your server](/docs/
|
|||||||
|
|
||||||
## Linking
|
## Linking
|
||||||
|
|
||||||
Find the server you would like to own, 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
|
||||||
|
|
||||||
Your server must have an author in-order to be automagically linked, and if it doesn't have an author, that means you will have to manually link your server. To do that, make an issue on GitHub, showing that your server has no author, but needs to be linked. Show proof that you own the server, and your account will own the server you need.
|
Your server must have an author in-order to be automagically linked, and if it doesn't have an author, that means you will have to manually link your server. To do that, make an issue on GitHub, showing that your server has no author, but needs to be linked. Show proof that you own the server, along with your account username, and your account will own the server you need.
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
import { withContentlayer } from "next-contentlayer";
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const nextConfig = {
|
const nextConfig = {
|
||||||
images: {
|
images: {
|
||||||
@ -8,6 +10,16 @@ const nextConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
async redirects() {
|
||||||
|
return [
|
||||||
|
// Basic redirect
|
||||||
|
{
|
||||||
|
source: '/docs',
|
||||||
|
destination: '/docs/getting-started',
|
||||||
|
permanent: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default nextConfig;
|
export default withContentlayer(nextConfig);
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "yarn@1.22.22",
|
"packageManager": "yarn@1.22.22",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbo",
|
"dev": "next dev",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
@ -23,7 +23,9 @@
|
|||||||
"@unocss/postcss": "^0.61.5",
|
"@unocss/postcss": "^0.61.5",
|
||||||
"@unocss/transformer-directives": "^0.61.5",
|
"@unocss/transformer-directives": "^0.61.5",
|
||||||
"@unocss/webpack": "^0.61.5",
|
"@unocss/webpack": "^0.61.5",
|
||||||
|
"contentlayer": "^0.3.4",
|
||||||
"discord.js": "^14.15.3",
|
"discord.js": "^14.15.3",
|
||||||
|
"github-slugger": "^2.0.0",
|
||||||
"inngest": "^3.21.2",
|
"inngest": "^3.21.2",
|
||||||
"input-otp": "^1.2.4",
|
"input-otp": "^1.2.4",
|
||||||
"json-beautify": "^1.1.1",
|
"json-beautify": "^1.1.1",
|
||||||
@ -31,6 +33,7 @@
|
|||||||
"minimessage-2-html": "1.6.0",
|
"minimessage-2-html": "1.6.0",
|
||||||
"mongodb": "^6.8.0",
|
"mongodb": "^6.8.0",
|
||||||
"next": "14.2.3",
|
"next": "14.2.3",
|
||||||
|
"next-contentlayer": "^0.3.4",
|
||||||
"next-css-obfuscator": "^2.2.16",
|
"next-css-obfuscator": "^2.2.16",
|
||||||
"next-themes": "^0.3.0",
|
"next-themes": "^0.3.0",
|
||||||
"nextjs-toploader": "^1.6.12",
|
"nextjs-toploader": "^1.6.12",
|
||||||
@ -40,6 +43,7 @@
|
|||||||
"react": "^18",
|
"react": "^18",
|
||||||
"react-dom": "^18",
|
"react-dom": "^18",
|
||||||
"react-fade-in": "^2.0.1",
|
"react-fade-in": "^2.0.1",
|
||||||
|
"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",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|||||||
367
src/app/account/settings/options/page.tsx
Normal file
367
src/app/account/settings/options/page.tsx
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
"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";
|
||||||
|
|
||||||
|
export default function Settings() {
|
||||||
|
const clerk = useClerk();
|
||||||
|
|
||||||
|
const { user, isSignedIn } = useUser();
|
||||||
|
const [linked, setLinked] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
setLinked(user?.publicMetadata.player != undefined);
|
||||||
|
}, [user, isSignedIn]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<main className="pt-[48px] 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>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<div className="md:grid md:grid-cols-3 gap-1.5">
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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.",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
export 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export 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,12 +1,13 @@
|
|||||||
"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, Link, UserPen } from "lucide-react";
|
import { ExternalLink, KeyRound, UserPen, Link, Cog } from "lucide-react";
|
||||||
import {
|
import {
|
||||||
ResizableHandle,
|
ResizableHandle,
|
||||||
ResizablePanel,
|
ResizablePanel,
|
||||||
ResizablePanelGroup,
|
ResizablePanelGroup,
|
||||||
} from "@/components/ui/resizable";
|
} 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";
|
||||||
@ -15,120 +16,154 @@ import { DialogContent, DialogTrigger } from "@/components/ui/dialog";
|
|||||||
import CodeDialog from "@/components/misc/LinkDialog";
|
import CodeDialog from "@/components/misc/LinkDialog";
|
||||||
|
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const clerk = useClerk();
|
const clerk = useClerk();
|
||||||
|
|
||||||
const { user, isSignedIn } = useUser();
|
const { user, isSignedIn } = useUser();
|
||||||
const [linked, setLinked] = useState(false);
|
const [linked, setLinked] = useState(false);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLinked(user?.publicMetadata.player != undefined);
|
setLinked(user?.publicMetadata.player != undefined);
|
||||||
}, [user, isSignedIn]);
|
}, [user, isSignedIn]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="pt-[48px] p-4">
|
<main className="pt-[48px] p-4">
|
||||||
<ResizablePanelGroup
|
<ResizablePanelGroup
|
||||||
direction="horizontal"
|
direction="horizontal"
|
||||||
className="min-h-[calc(100vh-70px)] "
|
className="min-h-[calc(100vh-70px)]"
|
||||||
>
|
>
|
||||||
<ResizablePanel className="min-w-[285px] max-w-[285px] w-[285px]">
|
<ResizablePanel className="max-md:hidden min-w-[285px] max-w-[285px] w-[285px]">
|
||||||
<div className="w-[300px] mt-[20px] ml-[10px]">
|
<div className="w-[300px] mt-[20px] ml-[10px]">
|
||||||
<Button className="mb-[2px] w-[250px]">
|
<Button className="mb-[2px] w-[250px]">
|
||||||
<Link size={16} className="mr-2" /> Linking
|
<Link size={16} className="mr-2" /> Linking
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<NextLink href="/account/settings/options" className="text-inherit">
|
||||||
className="mb-[2px] w-[250px]"
|
<Button className="mb-[2px] w-[250px] " variant="ghost">
|
||||||
variant="ghost"
|
<Cog size={16} className="mr-2" /> Options
|
||||||
onClick={() => clerk.openUserProfile({})}
|
</Button>
|
||||||
>
|
</NextLink>
|
||||||
<UserPen size={16} className="mr-2" /> Profile{" "}
|
<Button
|
||||||
<ExternalLink size={16} className="ml-2" />
|
className="mb-[2px] w-[250px]"
|
||||||
</Button>
|
variant="ghost"
|
||||||
<Button
|
onClick={() => clerk.openUserProfile({})}
|
||||||
className="mb-[2px] w-[250px]"
|
>
|
||||||
variant="ghost"
|
<UserPen size={16} className="mr-2" /> Profile{" "}
|
||||||
onClick={() => clerk.openUserProfile({})}
|
<ExternalLink size={16} className="ml-2" />
|
||||||
>
|
</Button>
|
||||||
<KeyRound size={16} className="mr-2" /> Security{" "}
|
<Button
|
||||||
<ExternalLink size={16} className="ml-2" />
|
className="mb-[2px] w-[250px]"
|
||||||
</Button>
|
variant="ghost"
|
||||||
</div>
|
onClick={() => clerk.openUserProfile({})}
|
||||||
</ResizablePanel>
|
>
|
||||||
<ResizableHandle />
|
<KeyRound size={16} className="mr-2" /> Security{" "}
|
||||||
<ResizablePanel>
|
<ExternalLink size={16} className="ml-2" />
|
||||||
<div className="p-4">
|
</Button>
|
||||||
<strong className="text-3xl">Linking</strong>
|
</div>
|
||||||
<br />
|
</ResizablePanel>
|
||||||
<br />
|
<ResizableHandle className="max-md:hidden" />
|
||||||
<strong className="font-bold">Link Account</strong>
|
<ResizablePanel>
|
||||||
<div className="flex items-center">
|
<div className="p-4">
|
||||||
<p>
|
<div className="md:hidden">
|
||||||
Link a Minecraft account to customize a server you own.
|
<Button className="mr-[2px]">
|
||||||
<br />{" "}
|
<Link size={16} className="mr-2" /> Linking
|
||||||
{user?.publicMetadata.player != undefined && linked && (
|
</Button>
|
||||||
<>
|
<NextLink
|
||||||
Currently linked to {user?.publicMetadata.player as string}
|
href="/account/settings/options"
|
||||||
</>
|
className="text-inherit"
|
||||||
)}
|
>
|
||||||
</p>
|
<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>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
<strong className="font-bold">Link Account</strong>
|
||||||
|
<div className="flex items-center">
|
||||||
|
<p>
|
||||||
|
Link a Minecraft account to customize a server you own.
|
||||||
|
<br />{" "}
|
||||||
|
{user?.publicMetadata.player != undefined && linked && (
|
||||||
|
<>
|
||||||
|
Currently linked to {user?.publicMetadata.player as string}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
|
||||||
<Dialog>
|
<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
|
||||||
linked={linked}
|
linked={linked}
|
||||||
setLinked={(c) => {
|
setLinked={(c) => {
|
||||||
setLinked(c);
|
setLinked(c);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{linked && (
|
{linked && (
|
||||||
<Button className="h-[30px] ml-2" disabled>
|
<Button className="h-[30px] ml-2" disabled>
|
||||||
Already linked
|
Already linked
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<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.
|
Unlink your Minecraft acconut if you have already linked one.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{!linked && (
|
{!linked && (
|
||||||
<Button className="h-[30px] ml-2" disabled>
|
<Button className="h-[30px] ml-2" disabled>
|
||||||
No linked account
|
No linked account
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{linked && (
|
{linked && (
|
||||||
<Button
|
<Button
|
||||||
className="h-[30px] ml-2"
|
className="h-[30px] ml-2"
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await toast.promise(unlinkMCAccount(), {
|
await toast.promise(unlinkMCAccount(), {
|
||||||
success: "Unlinked account!",
|
success: "Unlinked account!",
|
||||||
loading: "Unlinking...",
|
loading: "Unlinking...",
|
||||||
error: "Error while unlinking account.",
|
error: "Error while unlinking account.",
|
||||||
});
|
});
|
||||||
setLinked(false);
|
setLinked(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Unlink account
|
Unlink account
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</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 account links your Minecraft account.
|
another account links your Minecraft account.
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
89
src/app/docs/[[...slug]]/page.tsx
Normal file
89
src/app/docs/[[...slug]]/page.tsx
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
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 { notFound } from "next/navigation";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
|
|
||||||
|
export const generateStaticParams = async () =>
|
||||||
|
allDocs.map((post) => ({ slug: [post._raw.flattenedPath] }));
|
||||||
|
|
||||||
|
export const generateMetadata = ({
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
params: { slug: string[] };
|
||||||
|
}) => {
|
||||||
|
const post = allDocs.find(
|
||||||
|
(post) => post._raw.flattenedPath === params.slug.join("/")
|
||||||
|
);
|
||||||
|
if (!post) notFound();
|
||||||
|
return { title: post.title + " | MHSF Docs" };
|
||||||
|
};
|
||||||
|
|
||||||
|
const PostLayout = ({ params }: { params: { slug: string[] } }) => {
|
||||||
|
const doc = allDocs.find(
|
||||||
|
(post) => post._raw.flattenedPath === params.slug.join("/")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!doc) notFound();
|
||||||
|
console.log(doc);
|
||||||
|
const MDXContent = useMDXComponent(doc.body.code);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<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="pb-12 pt-8 prose dark:prose-invert">
|
||||||
|
<MDXContent components={{ Separator }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{doc.toc && (
|
||||||
|
<div className="hidden text-sm xl:block">
|
||||||
|
<div className="sticky top-16 -mt-10 pt-4">
|
||||||
|
<ScrollArea className="pb-10">
|
||||||
|
<div className="sticky top-16 -mt-10 h-[calc(100vh-3.5rem)] py-12 space-y-2">
|
||||||
|
<p className="font-medium">On This Page</p>
|
||||||
|
{doc.toc.map(
|
||||||
|
(c: { level: number; text: string; slug: string }) => (
|
||||||
|
<TableOfContent toc={c} doc={doc} />
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="font-medium">Contribute</p>
|
||||||
|
<ul className="m-0 list-none">
|
||||||
|
<li className="mt-0 pt-2">
|
||||||
|
<Link
|
||||||
|
href={
|
||||||
|
"https://github.com/DeveloLongScript/MHSF/edit/main/docs/" +
|
||||||
|
doc._raw.flattenedPath +
|
||||||
|
".mdx"
|
||||||
|
}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline-flex items-center text-sm text-muted-foreground hover:text-foreground transition-colors no-underline"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 438.549 438.549"
|
||||||
|
fontSize={16}
|
||||||
|
className="mr-2 size-4"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
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"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
Edit page on GitHub
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ScrollArea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default PostLayout;
|
||||||
44
src/app/docs/layout.tsx
Normal file
44
src/app/docs/layout.tsx
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { Sidebar } from "@/components/docs/Sidebar";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Drawer, DrawerContent, DrawerTrigger } from "@/components/ui/drawer";
|
||||||
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
|
import { version } from "@/config/version";
|
||||||
|
import { HamburgerMenuIcon } from "@radix-ui/react-icons";
|
||||||
|
|
||||||
|
export default async function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<div className="border-b pt-[40px]">
|
||||||
|
<div className="container flex-1 items-start md:grid md:grid-cols-[220px_minmax(0,1fr)] md:gap-6 lg:grid-cols-[240px_minmax(0,1fr)] lg:gap-10">
|
||||||
|
<aside className="fixed top-14 z-30 hidden h-[calc(100vh-3.5rem)] w-full shrink-0 md:sticky md:block">
|
||||||
|
<ScrollArea className="h-full py-6 pr-6 lg:py-8">
|
||||||
|
<div className="bg-muted w-full rounded justify-center p-4 flex items-center">
|
||||||
|
MHSF Docs <small className="ml-2">Version {version}</small>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
<Sidebar />
|
||||||
|
</ScrollArea>
|
||||||
|
</aside>
|
||||||
|
<br className="md:hidden" />
|
||||||
|
|
||||||
|
<div className="bg-muted w-full rounded justify-center p-4 flex items-center md:hidden">
|
||||||
|
MHSF Docs <small className="ml-2">Version {version}</small>
|
||||||
|
<Drawer>
|
||||||
|
<DrawerTrigger>
|
||||||
|
<Button className="ml-2">
|
||||||
|
<HamburgerMenuIcon />
|
||||||
|
</Button>
|
||||||
|
</DrawerTrigger>
|
||||||
|
<DrawerContent className="p-4">
|
||||||
|
<Sidebar />
|
||||||
|
</DrawerContent>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,37 +0,0 @@
|
|||||||
import Link from "next/link";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
export default function HelpPage() {
|
|
||||||
return (
|
|
||||||
<main className="sm:grid sm:grid-cols-6 pt-20">
|
|
||||||
<div className="pl-3 border rounded ml-4">
|
|
||||||
<strong className="pt-2">MHSF Help Guides</strong>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<L H="/">Go back to server list</L>
|
|
||||||
</div>
|
|
||||||
<div className="prose dark:prose-invert p-4 pl-[50px] max-w-[100%] col-span-5">
|
|
||||||
<h1>How to customize your server</h1>
|
|
||||||
<p>
|
|
||||||
Customizing a part of your server is easy, as long as you have access
|
|
||||||
to the account the server is owned under. To own a server, first go to
|
|
||||||
the server from the <L H="/">server list</L>. Make sure you own an
|
|
||||||
account by linking an account or creating a new one,{" "}
|
|
||||||
<L H="/help/how-to-link">
|
|
||||||
and make sure you linked your Minecraft account.
|
|
||||||
</L>{" "}
|
|
||||||
</p>
|
|
||||||
<h2>Guide</h2>
|
|
||||||
<p>
|
|
||||||
After going to the server page, click the Customization tab, and if
|
|
||||||
you own the server, click the button "Click to own this server". You
|
|
||||||
have successfully owned your Minehut server!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function L({ H, children }: { H: string; children: ReactNode }) {
|
|
||||||
return <Link href={H}>{children}</Link>;
|
|
||||||
}
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import Link from "next/link";
|
|
||||||
import { ReactNode } from "react";
|
|
||||||
|
|
||||||
export default function HelpPage() {
|
|
||||||
return (
|
|
||||||
<main className="sm:grid sm:grid-cols-6 pt-20">
|
|
||||||
<div className="pl-3 border rounded ml-4">
|
|
||||||
<strong className="pt-2">MHSF Help Guides</strong>
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<L H="/">Go back to server list</L>
|
|
||||||
</div>
|
|
||||||
<div className="prose dark:prose-invert p-4 pl-[50px] max-w-[100%] col-span-5">
|
|
||||||
<h1>How to link your Minecraft account</h1>
|
|
||||||
<p>
|
|
||||||
To link your Minecraft account, make sure you have a MHSF account
|
|
||||||
created, and go into the settings in the top right, and press
|
|
||||||
"Security/Profile settings". Click "Link Account".
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<h2>Joining the server</h2>
|
|
||||||
<p>
|
|
||||||
After launching Minecraft, join the server{" "}
|
|
||||||
<code>MHSFPV.minehut.gg</code> and take note of the code being said in
|
|
||||||
chat. <i>(You may need to go into the lobby to start up MHSFPV)</i>{" "}
|
|
||||||
Put this code the number selector, click "Submit", and you have linked
|
|
||||||
your account!
|
|
||||||
<br /> Congratulations!
|
|
||||||
</p>
|
|
||||||
<br />
|
|
||||||
<b>Related Articles:</b>
|
|
||||||
<p>
|
|
||||||
{" "}
|
|
||||||
- <L H="/help/how-to-customize">How to customize your server</L>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function L({ H, children }: { H: string; children: ReactNode }) {
|
|
||||||
return <Link href={H}>{children}</Link>;
|
|
||||||
}
|
|
||||||
@ -6,7 +6,7 @@ import { TooltipProvider } from "@/components/ui/tooltip";
|
|||||||
import { ThemeProvider } from "@/components/ThemeProvider";
|
import { ThemeProvider } from "@/components/ThemeProvider";
|
||||||
import { ClerkThemeProvider } from "@/components/clerk/ClerkThemeProvider";
|
import { ClerkThemeProvider } from "@/components/clerk/ClerkThemeProvider";
|
||||||
import NextTopLoader from "@/lib/top-loader";
|
import NextTopLoader from "@/lib/top-loader";
|
||||||
import { banner } from "@/banner";
|
import { banner } from "@/config/banner";
|
||||||
import {
|
import {
|
||||||
Breadcrumb,
|
Breadcrumb,
|
||||||
BreadcrumbList,
|
BreadcrumbList,
|
||||||
@ -48,7 +48,7 @@ export default async function RootLayout({
|
|||||||
(banner.isBanner == true ? "mt-8" : "")
|
(banner.isBanner == true ? "mt-8" : "")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="items-center me-auto mt-2 pl-7">
|
<div className="items-center me-auto mt-2 pl-7 max-sm:mt-3">
|
||||||
<Breadcrumb>
|
<Breadcrumb>
|
||||||
<BreadcrumbList>
|
<BreadcrumbList>
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
|
|||||||
@ -1,114 +0,0 @@
|
|||||||
import ClientFadeIn from "@/components/ClientFadeIn";
|
|
||||||
|
|
||||||
export default function ECA() {
|
|
||||||
return (
|
|
||||||
<main>
|
|
||||||
<div className="pt-[100px] p-[300px]">
|
|
||||||
<strong className="text-2xl">External Content Agreement (ECA)</strong>
|
|
||||||
<br />
|
|
||||||
By making external content available for anyone to see, there needs to
|
|
||||||
be an agreement to keep MHSF ("Minehut Server List") a friendly place
|
|
||||||
for anyone to look at.{" "}
|
|
||||||
<i>
|
|
||||||
As such, this agreement outlines what you can't and can do, when
|
|
||||||
making content on the platform.
|
|
||||||
</i>{" "}
|
|
||||||
The goal by making an agreement like this, is not to make you worried
|
|
||||||
what you can upload, its just showing what the limits of content
|
|
||||||
uploaded onto the platform are.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<ClientFadeIn>
|
|
||||||
<div>
|
|
||||||
<strong className="text-xl">Source Code</strong>
|
|
||||||
<br />
|
|
||||||
The source code for MHSF is defined by the{" "}
|
|
||||||
<a href="https://github.com/DeveloLongScript/MHSF/blob/main/LICENSE">
|
|
||||||
MIT License
|
|
||||||
</a>
|
|
||||||
. 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
|
|
||||||
<i>"forking"</i>) is also freely allowed.
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
</ClientFadeIn>
|
|
||||||
<br />
|
|
||||||
<ClientFadeIn delay={200}>
|
|
||||||
<div>
|
|
||||||
<strong className="text-xl">What your limits are</strong>
|
|
||||||
<br />
|
|
||||||
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.
|
|
||||||
<br />
|
|
||||||
For making banners & descriptions, you must follow{" "}
|
|
||||||
<a href="https://minehut.wiki.gg/wiki/Rules">
|
|
||||||
Minehuts Terms of Service
|
|
||||||
</a>{" "}
|
|
||||||
<i>
|
|
||||||
as all content made is associated to Minehut (as the server is
|
|
||||||
mostly on a community for Minehut).
|
|
||||||
</i>{" "}
|
|
||||||
<br />
|
|
||||||
For making Discord server embeds, you must follow{" "}
|
|
||||||
<a href="https://discord.com/terms/">
|
|
||||||
Discords Terms of Service
|
|
||||||
</a>{" "}
|
|
||||||
<i>as all content made is associated to Discord.</i> <br />
|
|
||||||
<strong>
|
|
||||||
For all other content, they must follow the following: <br />
|
|
||||||
</strong>
|
|
||||||
- No inappropriate/adult images <br />
|
|
||||||
- No swear words of any kind or slurs <br />- Endorsing unethical
|
|
||||||
client modifications (aka cheating or hacking)
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
</ClientFadeIn>
|
|
||||||
<br />
|
|
||||||
<ClientFadeIn delay={400}>
|
|
||||||
<div>
|
|
||||||
<strong className="text-xl">When you agree to the ECA</strong>
|
|
||||||
<br />
|
|
||||||
When you add customization to your server, or add a profile picture
|
|
||||||
(linking an account is included), you must follow the ECA.
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
</ClientFadeIn>
|
|
||||||
<br />
|
|
||||||
<ClientFadeIn delay={600}>
|
|
||||||
<div>
|
|
||||||
<strong className="text-xl">When linking an account</strong>
|
|
||||||
<br />
|
|
||||||
When linking an account, you must follow the privacy policy and
|
|
||||||
terms of service to the associated service that you linked your MHSF
|
|
||||||
account to. Additionally, if you link an external account{" "}
|
|
||||||
<i>after</i> account creation, everything said before is still true.
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
</ClientFadeIn>
|
|
||||||
<br />
|
|
||||||
<ClientFadeIn delay={800}>
|
|
||||||
<div>
|
|
||||||
<strong className="text-xl">Violations</strong>
|
|
||||||
<br />
|
|
||||||
Violations from above have 1 warning. Your first interaction is a
|
|
||||||
warning by removing the content from MHSF, and your 2nd is
|
|
||||||
banning/deleting your account. (some violations are an instant
|
|
||||||
delete)
|
|
||||||
</div>
|
|
||||||
</ClientFadeIn>
|
|
||||||
<br />
|
|
||||||
<ClientFadeIn delay={1000}>
|
|
||||||
<div>
|
|
||||||
<strong className="text-xl">Reporting</strong>
|
|
||||||
<br />
|
|
||||||
If you personally see a violation of the ECA, you can report it by
|
|
||||||
clicking the customization tab on a server, and hitting the Report
|
|
||||||
button (it doesn't appear when the server was never owned). If you
|
|
||||||
misuse this feature, you may get your account deleted.
|
|
||||||
</div>
|
|
||||||
</ClientFadeIn>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,28 +1,16 @@
|
|||||||
import {
|
import Link from "next/link";
|
||||||
Breadcrumb,
|
|
||||||
BreadcrumbList,
|
|
||||||
BreadcrumbItem,
|
|
||||||
BreadcrumbPage,
|
|
||||||
BreadcrumbSeparator,
|
|
||||||
} from "@/components/ui/breadcrumb";
|
|
||||||
import { Server } from "lucide-react";
|
|
||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
return (
|
return (
|
||||||
<div className="w-screen h-12 border-b fixed backdrop-blur flex">
|
<main>
|
||||||
<div className="me-auto mt-3 pl-7">
|
<div className="pt-[60px] p-4">
|
||||||
<Breadcrumb>
|
<strong>404 - Page not found</strong>
|
||||||
<BreadcrumbList>
|
<br />
|
||||||
<BreadcrumbItem className="max-sm:hidden">
|
<p>
|
||||||
<Server />
|
We couldn't find the page you were looking for.{" "}
|
||||||
</BreadcrumbItem>
|
<Link href="/">Go home</Link>
|
||||||
<BreadcrumbSeparator className="max-sm:hidden" />
|
</p>
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbPage>Not Found</BreadcrumbPage>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</Breadcrumb>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,9 @@ import TabServer from "@/components/misc/TabServer";
|
|||||||
import ColorProvider from "@/components/ColorProvider";
|
import ColorProvider from "@/components/ColorProvider";
|
||||||
import AfterServerView from "@/components/AfterServerView";
|
import AfterServerView from "@/components/AfterServerView";
|
||||||
import Banner from "@/components/Banner";
|
import Banner from "@/components/Banner";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { CornerDownLeft } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
params: { server: string };
|
params: { server: string };
|
||||||
@ -69,6 +72,12 @@ export default function ServerPage({ params }: { params: { server: string } }) {
|
|||||||
<ColorProvider server={params.server}>
|
<ColorProvider server={params.server}>
|
||||||
<div className={"pt-16"}>
|
<div className={"pt-16"}>
|
||||||
<Banner server={params.server} />
|
<Banner server={params.server} />
|
||||||
|
<Link href="/">
|
||||||
|
<Button variant="link" className="text-muted-foreground text-sm">
|
||||||
|
<CornerDownLeft size={16} className="mr-2" /> Go back to the
|
||||||
|
server list
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
<TabServer server={params.server} tabDef="general" />
|
<TabServer server={params.server} tabDef="general" />
|
||||||
<div className="pt-8">
|
<div className="pt-8">
|
||||||
<ServerView server={params.server} />
|
<ServerView server={params.server} />
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import FavoriteSortView from "@/components/FavoritesSortView";
|
import FavoriteSortView from "@/components/FavoritesSortView";
|
||||||
import { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Favorites Sort | MHSF",
|
title: "Favorites Sort | MHSF",
|
||||||
description: "See all of the servers on Minehut in order of favorites.",
|
description: "See all of the servers on Minehut in order of favorites.",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function FavoritesSort() {
|
export default function FavoritesSort() {
|
||||||
return (
|
return (
|
||||||
<main>
|
<main>
|
||||||
<div className="pt-[60px] p-4">
|
<div className="pt-[60px] p-4">
|
||||||
<FavoriteSortView />
|
<FavoriteSortView />
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,36 +1,36 @@
|
|||||||
import {
|
import {
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
ContextMenuTrigger,
|
ContextMenuTrigger,
|
||||||
ContextMenuItem,
|
ContextMenuItem,
|
||||||
ContextMenuContent,
|
ContextMenuContent,
|
||||||
ContextMenuSeparator,
|
ContextMenuSeparator,
|
||||||
} from "@/components/ui/context-menu";
|
} from "@/components/ui/context-menu";
|
||||||
import toast, { LoaderIcon } from "react-hot-toast";
|
import toast, { LoaderIcon } from "react-hot-toast";
|
||||||
import {
|
import {
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
} from "./ui/card";
|
} from "./ui/card";
|
||||||
import IconDisplay from "./IconDisplay";
|
import IconDisplay from "./IconDisplay";
|
||||||
import { TagShower } from "./ServerList";
|
import { TagShower } from "./ServerList";
|
||||||
import {
|
import {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
ChartArea,
|
ChartArea,
|
||||||
Copy,
|
Copy,
|
||||||
EllipsisVertical,
|
EllipsisVertical,
|
||||||
Layers,
|
Layers,
|
||||||
Star,
|
Star,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "./ui/button";
|
import { Button } from "./ui/button";
|
||||||
import {
|
import {
|
||||||
Drawer,
|
Drawer,
|
||||||
DrawerContent,
|
DrawerContent,
|
||||||
DrawerFooter,
|
DrawerFooter,
|
||||||
DrawerHeader,
|
DrawerHeader,
|
||||||
DrawerTitle,
|
DrawerTitle,
|
||||||
DrawerTrigger,
|
DrawerTrigger,
|
||||||
} from "@/components/ui/drawer";
|
} from "@/components/ui/drawer";
|
||||||
import { Tooltip } from "@radix-ui/react-tooltip";
|
import { Tooltip } from "@radix-ui/react-tooltip";
|
||||||
import { TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
import { TooltipContent, TooltipTrigger } from "./ui/tooltip";
|
||||||
@ -41,276 +41,276 @@ import { favoriteServer, isFavorited } from "@/lib/api";
|
|||||||
import { useUser } from "@clerk/nextjs";
|
import { useUser } from "@clerk/nextjs";
|
||||||
import { useTheme } from "next-themes";
|
import { useTheme } from "next-themes";
|
||||||
import {
|
import {
|
||||||
HoverCard,
|
HoverCard,
|
||||||
HoverCardContent,
|
HoverCardContent,
|
||||||
HoverCardTrigger,
|
HoverCardTrigger,
|
||||||
} from "@/components/ui/hover-card";
|
} from "@/components/ui/hover-card";
|
||||||
import useClipboard from "@/lib/useClipboard";
|
import useClipboard from "@/lib/useClipboard";
|
||||||
|
|
||||||
export default function ServerCard({ b, motd, mini, favs }: any) {
|
export default function ServerCard({ b, motd, mini, favs }: any) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const clipboard = useClipboard();
|
const clipboard = useClipboard();
|
||||||
const [favoriteStar, setFavoriteStar] = useState(false);
|
const [favoriteStar, setFavoriteStar] = useState(false);
|
||||||
const [favoriteLoading, setFavoriteLoading] = useState(true);
|
const [favoriteLoading, setFavoriteLoading] = useState(true);
|
||||||
const { isSignedIn } = useUser();
|
const { isSignedIn } = useUser();
|
||||||
const { resolvedTheme } = useTheme();
|
const { resolvedTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
if (open && isSignedIn)
|
if (open && isSignedIn)
|
||||||
isFavorited(b.name).then((c) => {
|
isFavorited(b.name).then((c) => {
|
||||||
setFavoriteStar(c);
|
setFavoriteStar(c);
|
||||||
setFavoriteLoading(false);
|
setFavoriteLoading(false);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<Card
|
<Card
|
||||||
key={b.name}
|
key={b.name}
|
||||||
className={
|
className={
|
||||||
(!mini ? "min-h-[450px] max-h-[450px]" : "") +
|
(!mini ? "min-h-[450px] max-h-[450px]" : "") +
|
||||||
" mb-4 flex items-start"
|
" mb-4 flex items-start shadow-md"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="m-0">
|
<CardTitle className="m-0">
|
||||||
<span>
|
<span>
|
||||||
<IconDisplay server={b} />
|
<IconDisplay server={b} />
|
||||||
<HoverCard>
|
<HoverCard>
|
||||||
<HoverCardTrigger asChild>
|
<HoverCardTrigger asChild>
|
||||||
<Link href={"/server/" + b.name}>
|
<Link href={"/server/" + b.name}>
|
||||||
<Button
|
<Button
|
||||||
variant={"link"}
|
variant={"link"}
|
||||||
className="text-2xl px-0 pl-1 font-semibold"
|
className="text-2xl px-0 pl-1 font-semibold"
|
||||||
>
|
>
|
||||||
{b.name}
|
{b.name}
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</HoverCardTrigger>
|
</HoverCardTrigger>
|
||||||
<HoverCardContent className="w-80 font-normal tracking-normal">
|
<HoverCardContent className="w-80 font-normal tracking-normal">
|
||||||
<div className="flex justify-between space-x-4">
|
<div className="flex justify-between space-x-4">
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<h4 className="text-sm font-semibold">{b.name}</h4>
|
<h4 className="text-sm font-semibold">{b.name}</h4>
|
||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
{motd && (
|
{motd && (
|
||||||
<span
|
<span
|
||||||
dangerouslySetInnerHTML={{ __html: motd }}
|
dangerouslySetInnerHTML={{ __html: motd }}
|
||||||
className="w-[30px] text-center break-all overflow-hidden"
|
className="w-[30px] text-center break-all overflow-hidden"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
<div className="flex items-center pt-2">
|
<div className="flex items-center pt-2">
|
||||||
<span className="text-xs text-muted-foreground flex items-center">
|
<span className="text-xs text-muted-foreground flex items-center">
|
||||||
<ArrowRight size={16} className="mr-2" />
|
<ArrowRight size={16} className="mr-2" />
|
||||||
Open Server Page
|
Open Server Page
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center pt-2">
|
<div className="flex items-center pt-2">
|
||||||
<span className="text-xs text-muted-foreground flex items-center">
|
<span className="text-xs text-muted-foreground flex items-center">
|
||||||
<ChartArea size={16} className="mr-2" />
|
<ChartArea size={16} className="mr-2" />
|
||||||
Running on{" "}
|
Running on{" "}
|
||||||
{b.staticInfo.serverPlan == undefined
|
{b.staticInfo.serverPlan == undefined
|
||||||
? "Free Plan"
|
? "Free Plan"
|
||||||
: b.staticInfo.serverPlan}
|
: b.staticInfo.serverPlan}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</HoverCardContent>
|
</HoverCardContent>
|
||||||
</HoverCard>
|
</HoverCard>
|
||||||
</span>
|
</span>
|
||||||
<Drawer>
|
<Drawer>
|
||||||
<DrawerTrigger>
|
<DrawerTrigger>
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
className="w-[24px] h-[24px] ml-2 md:hidden"
|
className="w-[24px] h-[24px] ml-2 md:hidden"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
>
|
>
|
||||||
<EllipsisVertical size={16} />
|
<EllipsisVertical size={16} />
|
||||||
</Button>
|
</Button>
|
||||||
</DrawerTrigger>
|
</DrawerTrigger>
|
||||||
<DrawerContent>
|
<DrawerContent>
|
||||||
<DrawerHeader>
|
<DrawerHeader>
|
||||||
<DrawerTitle>Actions</DrawerTitle>
|
<DrawerTitle>Actions</DrawerTitle>
|
||||||
</DrawerHeader>
|
</DrawerHeader>
|
||||||
<DrawerFooter>
|
<DrawerFooter>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
||||||
toast.success("Copied IP to clipboard");
|
toast.success("Copied IP to clipboard");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Copy server IP
|
Copy server IP
|
||||||
<Copy size={18} className="ml-4" />
|
<Copy size={18} className="ml-4" />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push("/server/" + b.name);
|
router.push("/server/" + b.name);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Open server page
|
Open server page
|
||||||
</Button>
|
</Button>
|
||||||
</DrawerFooter>
|
</DrawerFooter>
|
||||||
</DrawerContent>
|
</DrawerContent>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
{b.author != undefined ? (
|
{b.author != undefined ? (
|
||||||
<div className="text-sm text-muted-foreground font-normal tracking-normal">
|
<div className="text-sm text-muted-foreground font-normal tracking-normal">
|
||||||
by {b.author}
|
by {b.author}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<br />
|
<br />
|
||||||
)}
|
)}
|
||||||
<TagShower server={b} />
|
<TagShower server={b} />
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<CardDescription className="float-left inline ">
|
<CardDescription className="float-left inline ">
|
||||||
<span className="flex items-center">
|
<span className="flex items-center">
|
||||||
{b.playerData.playerCount == 0 ? (
|
{b.playerData.playerCount == 0 ? (
|
||||||
<div
|
<div
|
||||||
className="items-center border"
|
className="items-center border"
|
||||||
style={{
|
style={{
|
||||||
width: ".5rem",
|
width: ".5rem",
|
||||||
height: ".5rem",
|
height: ".5rem",
|
||||||
borderRadius: "9999px",
|
borderRadius: "9999px",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
className="items-center"
|
className="items-center"
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "#0cce6b",
|
backgroundColor: "#0cce6b",
|
||||||
width: ".5rem",
|
width: ".5rem",
|
||||||
height: ".5rem",
|
height: ".5rem",
|
||||||
borderRadius: "9999px",
|
borderRadius: "9999px",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<span className="pl-1">
|
<span className="pl-1">
|
||||||
{b.playerData.playerCount}{" "}
|
{b.playerData.playerCount}{" "}
|
||||||
{b.playerData.playerCount == 1 ? "player" : "players"}{" "}
|
{b.playerData.playerCount == 1 ? "player" : "players"}{" "}
|
||||||
currently online {favs && <>• {favs} favorited</>}
|
currently online {favs && <>• {favs} favorited</>}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<ContextMenu>
|
<ContextMenu>
|
||||||
<ContextMenuTrigger>
|
<ContextMenuTrigger>
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="min-w-[128px] max-w-[328px] h-[32px] mt-2 ml-2 max-md:hidden"
|
className="min-w-[128px] max-w-[328px] h-[32px] mt-2 ml-2 max-md:hidden"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
||||||
toast.success("Copied IP to clipboard");
|
toast.success("Copied IP to clipboard");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Copy size={18} />
|
<Copy size={18} />
|
||||||
<code className="ml-2">{b.name}</code>
|
<code className="ml-2">{b.name}</code>
|
||||||
</Button>
|
</Button>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<Link href={"/server/" + b.name}>
|
<Link href={"/server/" + b.name}>
|
||||||
<Button
|
<Button
|
||||||
size="icon"
|
size="icon"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
className="w-[32px] h-[32px] mt-2 ml-2 max-md:hidden"
|
className="w-[32px] h-[32px] mt-2 ml-2 max-md:hidden"
|
||||||
>
|
>
|
||||||
<Layers size={18} />
|
<Layers size={18} />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
Open up the server page to see more information about
|
Open up the server page to see more information about
|
||||||
the server
|
the server
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>
|
</>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
||||||
toast.success("Copied IP to clipboard");
|
toast.success("Copied IP to clipboard");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Copy server IP
|
Copy server IP
|
||||||
<div className="RightSlot">
|
<div className="RightSlot">
|
||||||
<Copy size={18} />
|
<Copy size={18} />
|
||||||
</div>
|
</div>
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
<Link href={"/server/" + b.name}>
|
<Link href={"/server/" + b.name}>
|
||||||
<ContextMenuItem>Open server page</ContextMenuItem>
|
<ContextMenuItem>Open server page</ContextMenuItem>
|
||||||
</Link>
|
</Link>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
{motd && (
|
{motd && (
|
||||||
<span
|
<span
|
||||||
dangerouslySetInnerHTML={{ __html: motd }}
|
dangerouslySetInnerHTML={{ __html: motd }}
|
||||||
className="w-[30px] text-center break-all overflow-hidden"
|
className="w-[30px] text-center break-all overflow-hidden"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
</ContextMenuTrigger>
|
</ContextMenuTrigger>
|
||||||
<ContextMenuContent>
|
<ContextMenuContent>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
clipboard.writeText(b.name + ".mshf.minehut.gg");
|
||||||
toast.success("Copied IP to clipboard");
|
toast.success("Copied IP to clipboard");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Copy server IP
|
Copy server IP
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push("/server/" + b.name);
|
router.push("/server/" + b.name);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Open server page
|
Open server page
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
router.push("/server/" + b.name + "/statistics");
|
router.push("/server/" + b.name + "/statistics");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Open statistics page
|
Open statistics page
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
<ContextMenuSeparator />
|
<ContextMenuSeparator />
|
||||||
<ContextMenuItem
|
<ContextMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setFavoriteLoading(true);
|
setFavoriteLoading(true);
|
||||||
favoriteServer(b.name).then(() => {
|
favoriteServer(b.name).then(() => {
|
||||||
setFavoriteLoading(false);
|
setFavoriteLoading(false);
|
||||||
setFavoriteStar(!favoriteStar);
|
setFavoriteStar(!favoriteStar);
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={!isSignedIn || favoriteLoading}
|
disabled={!isSignedIn || favoriteLoading}
|
||||||
>
|
>
|
||||||
{!favoriteLoading && (
|
{!favoriteLoading && (
|
||||||
<Star
|
<Star
|
||||||
size={16}
|
size={16}
|
||||||
className="mr-2 text-white"
|
className="mr-2 text-white"
|
||||||
fill={
|
fill={
|
||||||
favoriteStar
|
favoriteStar
|
||||||
? resolvedTheme == "dark"
|
? resolvedTheme == "dark"
|
||||||
? "white"
|
? "white"
|
||||||
: "black"
|
: "black"
|
||||||
: "transparent"
|
: "transparent"
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
)}{" "}
|
)}{" "}
|
||||||
{favoriteLoading && <LoaderIcon className="mr-2" />}
|
{favoriteLoading && <LoaderIcon className="mr-2" />}
|
||||||
{favoriteStar && isSignedIn ? "Unf" : "F"}avorite Server
|
{favoriteStar && isSignedIn ? "Unf" : "F"}avorite Server
|
||||||
</ContextMenuItem>
|
</ContextMenuItem>
|
||||||
</ContextMenuContent>
|
</ContextMenuContent>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -150,7 +150,7 @@ export default function ServerCustomize({
|
|||||||
Is this server in violation of the ECA?
|
Is this server in violation of the ECA?
|
||||||
</div>
|
</div>
|
||||||
Is this server in violation of the{" "}
|
Is this server in violation of the{" "}
|
||||||
<Link href="/legal/external-content-agreement">
|
<Link href="/docs/legal/external-content-agreement">
|
||||||
External Content Agreement (aka ECA)
|
External Content Agreement (aka ECA)
|
||||||
</Link>
|
</Link>
|
||||||
? You can report the server to remove the customizations from the
|
? You can report the server to remove the customizations from the
|
||||||
@ -165,9 +165,11 @@ export default function ServerCustomize({
|
|||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
This will send a notification to MHSF maintainers. This
|
This will send a notification to MHSF maintainers. This
|
||||||
server must be in violation of the{" "}
|
server must be in violation of the{" "}
|
||||||
<Link href="/legal/external-content-agreement">ECA</Link> to
|
<Link href="/docs/legal/external-content-agreement">
|
||||||
be a valid report. Typical response times include 1 hour to
|
ECA
|
||||||
1 day, and you will not be notified if your report is
|
</Link>{" "}
|
||||||
|
to be a valid report. Typical response times include 1 hour
|
||||||
|
to 1 day, and you will not be notified if your report is
|
||||||
successful or not.{" "}
|
successful or not.{" "}
|
||||||
<b>
|
<b>
|
||||||
Please do not spam this form with mindless reports. If you
|
Please do not spam this form with mindless reports. If you
|
||||||
@ -293,7 +295,7 @@ export default function ServerCustomize({
|
|||||||
Minehuts Terms of Service
|
Minehuts Terms of Service
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
& the{" "}
|
& the{" "}
|
||||||
<Link href="/legal/external-content-agreement">
|
<Link href="/docs/legal/external-content-agreement">
|
||||||
External Content Agreement
|
External Content Agreement
|
||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
@ -400,7 +402,7 @@ export default function ServerCustomize({
|
|||||||
Imgurs Terms of Service
|
Imgurs Terms of Service
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
& the{" "}
|
& the{" "}
|
||||||
<Link href="/legal/external-content-agreement">
|
<Link href="/docs/legal/external-content-agreement">
|
||||||
External Content Agreement
|
External Content Agreement
|
||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
@ -432,7 +434,7 @@ export default function ServerCustomize({
|
|||||||
Discords Terms of Service
|
Discords Terms of Service
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
& the{" "}
|
& the{" "}
|
||||||
<Link href="/legal/external-content-agreement">
|
<Link href="/docs/legal/external-content-agreement">
|
||||||
External Content Agreement
|
External Content Agreement
|
||||||
</Link>
|
</Link>
|
||||||
.
|
.
|
||||||
|
|||||||
@ -30,7 +30,7 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { allTags, allCategories } from "@/allTags";
|
import { allTags, allCategories } from "@/config/tags";
|
||||||
import IconDisplay from "./IconDisplay";
|
import IconDisplay from "./IconDisplay";
|
||||||
import InfiniteScroll from "react-infinite-scroll-component";
|
import InfiniteScroll from "react-infinite-scroll-component";
|
||||||
import { Spinner } from "./ui/spinner";
|
import { Spinner } from "./ui/spinner";
|
||||||
@ -77,7 +77,7 @@ const features = [
|
|||||||
name: "Add a Discord widget",
|
name: "Add a Discord widget",
|
||||||
description:
|
description:
|
||||||
"Show where your players talk to each-other, including an online users count.",
|
"Show where your players talk to each-other, including an online users count.",
|
||||||
href: "/help/how-to-customize",
|
href: "/docs/guides/customization",
|
||||||
cta: "Learn more",
|
cta: "Learn more",
|
||||||
background: <span />,
|
background: <span />,
|
||||||
className: "lg:row-start-1 lg:row-end-2 lg:col-start-2 lg:col-end-3",
|
className: "lg:row-start-1 lg:row-end-2 lg:col-start-2 lg:col-end-3",
|
||||||
@ -85,7 +85,7 @@ const features = [
|
|||||||
{
|
{
|
||||||
Icon: InputIcon,
|
Icon: InputIcon,
|
||||||
name: "Descriptions",
|
name: "Descriptions",
|
||||||
href: "/help/how-to-customize",
|
href: "/docs/guides/customization",
|
||||||
cta: "Learn more",
|
cta: "Learn more",
|
||||||
description:
|
description:
|
||||||
"Format your descriptions using Markdown to show what your server has to offer.",
|
"Format your descriptions using Markdown to show what your server has to offer.",
|
||||||
@ -95,7 +95,7 @@ const features = [
|
|||||||
{
|
{
|
||||||
Icon: ImageIcon,
|
Icon: ImageIcon,
|
||||||
name: "Banners",
|
name: "Banners",
|
||||||
href: "/help/how-to-customize",
|
href: "/docs/guides/customization",
|
||||||
cta: "Learn more",
|
cta: "Learn more",
|
||||||
description:
|
description:
|
||||||
"Show a banner with can contain images that show on your server page.",
|
"Show a banner with can contain images that show on your server page.",
|
||||||
@ -116,7 +116,7 @@ export default function ServerList() {
|
|||||||
const [random, setRandom] = useState(false);
|
const [random, setRandom] = useState(false);
|
||||||
const [serverList, setServerList] = useState(new ServersList([]));
|
const [serverList, setServerList] = useState(new ServersList([]));
|
||||||
const [textCopied, setTextCopied] = useState(false);
|
const [textCopied, setTextCopied] = useState(false);
|
||||||
const [padding, setPadding] = useState(0);
|
const [padding, setPadding] = useState<string>("0");
|
||||||
const bigger = async (server: OnlineServer) =>
|
const bigger = async (server: OnlineServer) =>
|
||||||
server.playerData.playerCount > 15;
|
server.playerData.playerCount > 15;
|
||||||
const smaller = async (server: OnlineServer) =>
|
const smaller = async (server: OnlineServer) =>
|
||||||
@ -128,8 +128,10 @@ export default function ServerList() {
|
|||||||
const [servers, setServers] = useState<Array<OnlineServer>>([]);
|
const [servers, setServers] = useState<Array<OnlineServer>>([]);
|
||||||
const clipboard = useClipboard();
|
const clipboard = useClipboard();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { user, isSignedIn } = useUser();
|
||||||
const [pOS, setpOS] = useState(false);
|
const [pOS, setpOS] = useState(false);
|
||||||
const [ipr, setIPR] = useState("4");
|
const [ipr, setIPR] = useState<string>("4");
|
||||||
|
const [am, setAM] = useState<boolean>(false);
|
||||||
const [filters, setFilters] = useState<
|
const [filters, setFilters] = useState<
|
||||||
Array<(server: OnlineServer) => Promise<boolean>>
|
Array<(server: OnlineServer) => Promise<boolean>>
|
||||||
>([]);
|
>([]);
|
||||||
@ -143,6 +145,16 @@ export default function ServerList() {
|
|||||||
setColor(resolvedTheme === "dark" ? "#ffffff" : "#000000");
|
setColor(resolvedTheme === "dark" ? "#ffffff" : "#000000");
|
||||||
}, [resolvedTheme]);
|
}, [resolvedTheme]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isSignedIn) {
|
||||||
|
console.log(user.publicMetadata);
|
||||||
|
setIPR((user.publicMetadata.ipr as string | undefined) || "4");
|
||||||
|
setPadding((user.publicMetadata.pad as string | undefined) || "0");
|
||||||
|
setpOS((user.publicMetadata.srv as boolean | undefined) || false);
|
||||||
|
setAM(pOS != false || padding != "0" || ipr != "4");
|
||||||
|
}
|
||||||
|
}, [isSignedIn, user]);
|
||||||
|
|
||||||
useEffectOnce(() => {
|
useEffectOnce(() => {
|
||||||
setRandomText(getRandomText());
|
setRandomText(getRandomText());
|
||||||
serverList
|
serverList
|
||||||
@ -172,7 +184,6 @@ export default function ServerList() {
|
|||||||
const ref = useRef(null);
|
const ref = useRef(null);
|
||||||
const [clickedPage, setClickedPage] = useState("banners");
|
const [clickedPage, setClickedPage] = useState("banners");
|
||||||
const [hero, setHero] = useState(false);
|
const [hero, setHero] = useState(false);
|
||||||
const { isSignedIn } = useUser();
|
|
||||||
if (inErrState) {
|
if (inErrState) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -211,11 +222,11 @@ export default function ServerList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={!pOS ? { padding } : undefined}>
|
<div style={!pOS ? { padding: `${padding}px` } : undefined}>
|
||||||
<div className="p-0 branding-hero">
|
<div className="p-0 branding-hero">
|
||||||
<>
|
<>
|
||||||
{(!isSignedIn || hero) && (
|
{(!isSignedIn || hero) && (
|
||||||
<div className=" py-2 h-[300vh] relative mx-auto mt-20 max-w-7xl px-6 text-center md:px-8 ">
|
<div className=" py-2 max-lg:h-[370vh] lg:h-[300vh] 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}
|
||||||
@ -474,7 +485,7 @@ export default function ServerList() {
|
|||||||
<br id="serverlist" className="pb-14" />
|
<br id="serverlist" className="pb-14" />
|
||||||
<Separator />
|
<Separator />
|
||||||
<ClientFadeIn delay={100}>
|
<ClientFadeIn delay={100}>
|
||||||
<Menubar className="mt-3 ml-2 border rounded p-2">
|
<Menubar className="mt-3 ml-2 border rounded p-2 shadow">
|
||||||
<MenubarMenu>
|
<MenubarMenu>
|
||||||
<MenubarTrigger>Servers</MenubarTrigger>
|
<MenubarTrigger>Servers</MenubarTrigger>
|
||||||
<MenubarContent>
|
<MenubarContent>
|
||||||
@ -856,7 +867,7 @@ export default function ServerList() {
|
|||||||
<MenubarSubContent>
|
<MenubarSubContent>
|
||||||
<MenubarRadioGroup
|
<MenubarRadioGroup
|
||||||
value={padding.toString()}
|
value={padding.toString()}
|
||||||
onValueChange={(v) => setPadding(Number(v))}
|
onValueChange={(v) => setPadding(v)}
|
||||||
>
|
>
|
||||||
<MenubarRadioItem value="0">Default</MenubarRadioItem>
|
<MenubarRadioItem value="0">Default</MenubarRadioItem>
|
||||||
<MenubarSeparator />
|
<MenubarSeparator />
|
||||||
@ -891,12 +902,20 @@ export default function ServerList() {
|
|||||||
</MenubarRadioGroup>
|
</MenubarRadioGroup>
|
||||||
</MenubarSubContent>
|
</MenubarSubContent>
|
||||||
</MenubarSub>
|
</MenubarSub>
|
||||||
|
<MenubarSeparator />
|
||||||
<SignedIn>
|
<SignedIn>
|
||||||
<MenubarSeparator />
|
|
||||||
<MenubarCheckboxItem checked={hero} onCheckedChange={setHero}>
|
<MenubarCheckboxItem checked={hero} onCheckedChange={setHero}>
|
||||||
Show Hero
|
Show Hero
|
||||||
</MenubarCheckboxItem>
|
</MenubarCheckboxItem>
|
||||||
</SignedIn>
|
</SignedIn>
|
||||||
|
<MenubarItem onClick={() => router.push("/docs")}>
|
||||||
|
View the docs
|
||||||
|
</MenubarItem>
|
||||||
|
{am && (
|
||||||
|
<MenubarItem onClick={() => router.push("/account/settings")}>
|
||||||
|
Currently using settings in your preferences
|
||||||
|
</MenubarItem>
|
||||||
|
)}
|
||||||
</MenubarContent>
|
</MenubarContent>
|
||||||
</MenubarMenu>
|
</MenubarMenu>
|
||||||
</Menubar>
|
</Menubar>
|
||||||
@ -1013,8 +1032,8 @@ export default function ServerList() {
|
|||||||
}
|
}
|
||||||
style={{
|
style={{
|
||||||
overflow: "hidden !important",
|
overflow: "hidden !important",
|
||||||
paddingLeft: pOS ? padding : 6,
|
paddingLeft: pOS ? `${padding}px` : 6,
|
||||||
paddingRight: pOS ? padding : 6,
|
paddingRight: pOS ? `${padding}px` : 6,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ClientFadeIn delay={200}>
|
<ClientFadeIn delay={200}>
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
import {
|
import {
|
||||||
BreadcrumbItem,
|
BreadcrumbItem,
|
||||||
BreadcrumbPage,
|
BreadcrumbPage,
|
||||||
BreadcrumbSeparator,
|
BreadcrumbSeparator,
|
||||||
} from "./ui/breadcrumb";
|
} from "./ui/breadcrumb";
|
||||||
|
import { allDocs } from "contentlayer/generated";
|
||||||
|
|
||||||
export default function TextFromPathname() {
|
export default function TextFromPathname() {
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
@ -72,6 +72,18 @@ export default function TextFromPathname() {
|
|||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{pathname == "/account/settings/options" && (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator className="max-sm:hidden" />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage>Settings</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage>Preferences</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{pathname == "/legal/external-content-agreement" && (
|
{pathname == "/legal/external-content-agreement" && (
|
||||||
<>
|
<>
|
||||||
<BreadcrumbSeparator className="max-sm:hidden" />
|
<BreadcrumbSeparator className="max-sm:hidden" />
|
||||||
@ -94,6 +106,27 @@ export default function TextFromPathname() {
|
|||||||
</BreadcrumbItem>
|
</BreadcrumbItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
{pathname?.startsWith("/docs/") && (
|
||||||
|
<>
|
||||||
|
<BreadcrumbSeparator className="max-sm:hidden" />
|
||||||
|
<BreadcrumbItem>Docs</BreadcrumbItem>
|
||||||
|
<BreadcrumbSeparator />
|
||||||
|
<BreadcrumbItem>
|
||||||
|
<BreadcrumbPage>
|
||||||
|
{
|
||||||
|
allDocs.find(
|
||||||
|
(c) =>
|
||||||
|
c._raw.flattenedPath ===
|
||||||
|
pathname
|
||||||
|
?.split("/")
|
||||||
|
.splice(2, pathname?.split("/").length)
|
||||||
|
.join("/")
|
||||||
|
)?.title
|
||||||
|
}
|
||||||
|
</BreadcrumbPage>
|
||||||
|
</BreadcrumbItem>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -37,7 +37,7 @@ export default function TopBar({ inter }: { inter: 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>
|
||||||
</Button>
|
</Button>
|
||||||
<ThemeSwitcher />
|
<ThemeSwitcher />
|
||||||
|
|||||||
107
src/components/docs/Sidebar.tsx
Normal file
107
src/components/docs/Sidebar.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
"use client";
|
||||||
|
import { allFolders, Docs, DocsFolder } from "@/config/docs";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import { Button } from "../ui/button";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ChevronRight } from "lucide-react";
|
||||||
|
import { useRouter } from "@/lib/useRouter";
|
||||||
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
|
|
||||||
|
export function Sidebar() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{allFolders.map((docs) => {
|
||||||
|
const [folderOpen, setOpen] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="w-full font-normal tracking-normal mt-1"
|
||||||
|
noJustify
|
||||||
|
variant={
|
||||||
|
"url" in docs
|
||||||
|
? pathname == docs.url
|
||||||
|
? "default"
|
||||||
|
: "ghost"
|
||||||
|
: "ghost"
|
||||||
|
}
|
||||||
|
onClick={() => {
|
||||||
|
if ("docs" in docs) {
|
||||||
|
setOpen(!folderOpen);
|
||||||
|
} else {
|
||||||
|
router.push(docs.url);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{"url" in docs ? docs.title : docs.name}
|
||||||
|
<div className="flex items-center ml-auto text-muted-foreground">
|
||||||
|
<AnimatePresence>
|
||||||
|
{"docs" in docs && folderOpen && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ rotate: 90 }}
|
||||||
|
animate={{ rotate: 0 }}
|
||||||
|
>
|
||||||
|
<ChevronRight size={18} />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
{"docs" in docs && !folderOpen && (
|
||||||
|
<motion.div
|
||||||
|
initial={{ rotate: 0 }}
|
||||||
|
animate={{ rotate: 90 }}
|
||||||
|
>
|
||||||
|
<ChevronRight size={18} />
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
<div className="ml-2">
|
||||||
|
{folderOpen && <Subdocs docs={"docs" in docs ? docs.docs : []} />}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Subdocs({ docs }: { docs: (Docs | DocsFolder)[] }) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{docs.map((doc) => {
|
||||||
|
if ("docs" in doc) {
|
||||||
|
return <Subdocs docs={doc.docs} />;
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="w-full font-normal tracking-normal mt-1"
|
||||||
|
noJustify
|
||||||
|
onClick={() => {
|
||||||
|
router.push(doc.url);
|
||||||
|
}}
|
||||||
|
variant={
|
||||||
|
"url" in doc
|
||||||
|
? pathname == doc.url
|
||||||
|
? "default"
|
||||||
|
: "ghost"
|
||||||
|
: "ghost"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{doc.title}
|
||||||
|
</Button>
|
||||||
|
<br />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
85
src/components/docs/TOC.tsx
Normal file
85
src/components/docs/TOC.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
"use client";
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
import { Docs } from "contentlayer/generated";
|
||||||
|
import { useEffect, useMemo, useState } from "react";
|
||||||
|
|
||||||
|
export default function TableOfContent({
|
||||||
|
doc,
|
||||||
|
toc,
|
||||||
|
}: {
|
||||||
|
doc: Docs;
|
||||||
|
toc: { level: number; text: string; slug: string };
|
||||||
|
}) {
|
||||||
|
const itemIds = useMemo(
|
||||||
|
() =>
|
||||||
|
doc?.toc.flatMap(
|
||||||
|
(c: { level: number; text: string; slug: string }) => c.slug
|
||||||
|
),
|
||||||
|
[doc]
|
||||||
|
);
|
||||||
|
const activeHeading = useActiveItem(itemIds);
|
||||||
|
|
||||||
|
return <Tree item={toc} activeItem={activeHeading} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function useActiveItem(itemIds: string[]) {
|
||||||
|
const [activeId, setActiveId] = useState<any>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const observer = new IntersectionObserver(
|
||||||
|
(entries) => {
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setActiveId(entry.target.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
{ rootMargin: `0% 0% -80% 0%` }
|
||||||
|
);
|
||||||
|
|
||||||
|
itemIds?.forEach((id) => {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
observer.observe(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
itemIds?.forEach((id) => {
|
||||||
|
const element = document.getElementById(id);
|
||||||
|
if (element) {
|
||||||
|
observer.unobserve(element);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}, [itemIds]);
|
||||||
|
|
||||||
|
return activeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TreeProps {
|
||||||
|
item: { level: number; text: string; slug: string };
|
||||||
|
activeItem?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Tree({ item, activeItem }: TreeProps) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ul className={cn("m-0 list-none", { "pl-4": item.level !== 1 })}>
|
||||||
|
<li key={item.text} className={cn("mt-0 pt-2")}>
|
||||||
|
<a
|
||||||
|
href={"#" + item.slug}
|
||||||
|
className={cn(
|
||||||
|
"inline-block no-underline transition-colors hover:text-foreground",
|
||||||
|
item.slug === `${activeItem}`
|
||||||
|
? "font-medium text-foreground"
|
||||||
|
: "text-muted-foreground"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{item.text}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,13 +1,15 @@
|
|||||||
import { Button } from "../ui/button";
|
import { Button } from "../ui/button";
|
||||||
import { Calendar, Star, TerminalIcon } from "lucide-react";
|
import { Book, Calendar, Star, TerminalIcon } from "lucide-react";
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
|
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Changelog, version } from "@/version";
|
import { Changelog, version } from "@/config/version";
|
||||||
import events from "@/lib/commandEvent";
|
import events from "@/lib/commandEvent";
|
||||||
import { ScrollArea } from "../ui/scroll-area";
|
import { ScrollArea } from "../ui/scroll-area";
|
||||||
|
import { useRouter } from "@/lib/useRouter";
|
||||||
|
|
||||||
export default function InfoPopover() {
|
export default function InfoPopover() {
|
||||||
const [changeLog, setChangelog] = useState(false);
|
const [changeLog, setChangelog] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid w-full">
|
<div className="grid w-full">
|
||||||
@ -43,6 +45,9 @@ export default function InfoPopover() {
|
|||||||
>
|
>
|
||||||
<Star size={18} className="mr-2" /> Star on GitHub
|
<Star size={18} className="mr-2" /> Star on GitHub
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button variant={"ghost"} onClick={() => router.push("/docs")}>
|
||||||
|
<Book size={18} className="mr-2" /> See the docs
|
||||||
|
</Button>
|
||||||
<Button variant="ghost" onClick={() => events.emit("cmd-event")}>
|
<Button variant="ghost" onClick={() => events.emit("cmd-event")}>
|
||||||
<TerminalIcon size={18} className="mr-2" /> Open commands
|
<TerminalIcon size={18} className="mr-2" /> Open commands
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
48
src/config/docs.ts
Normal file
48
src/config/docs.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
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" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export type Docs = {
|
||||||
|
title: string;
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DocsFolder = {
|
||||||
|
name: string;
|
||||||
|
docs: Array<Docs>;
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { OnlineServer, ServerResponse } from "./lib/types/mh-server";
|
import { OnlineServer, ServerResponse } from "@/lib/types/mh-server";
|
||||||
|
|
||||||
const serverCache: any = {};
|
const serverCache: any = {};
|
||||||
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Separator } from "./components/ui/separator";
|
import { Separator } from "../components/ui/separator";
|
||||||
import { Button } from "./components/ui/button";
|
import { Button } from "../components/ui/button";
|
||||||
import confetti from "canvas-confetti";
|
import confetti from "canvas-confetti";
|
||||||
|
|
||||||
export const version = "1.1.0";
|
export const version = "1.2.0";
|
||||||
|
|
||||||
const User = ({ user }: { user: string }) => (
|
const User = ({ user }: { user: string }) => (
|
||||||
<span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]">
|
<span className="cursor-pointer bg-[rgba(255,165,0,0.25);] rounded p-[2.5px]">
|
||||||
@ -77,6 +77,18 @@ export const Changelog = () => (
|
|||||||
</code>
|
</code>
|
||||||
</div>
|
</div>
|
||||||
<br />
|
<br />
|
||||||
|
<div>
|
||||||
|
<strong className="flex items-center">
|
||||||
|
Version 1.2.0 (September 3rd 2024)
|
||||||
|
</strong>
|
||||||
|
<ul>
|
||||||
|
<li>• Added documentation</li>
|
||||||
|
<li>• Brand new linking of padding and server options</li>
|
||||||
|
<li>• New system to ensure automatic Cron actions!</li>
|
||||||
|
<li>• and alot more!</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
<div>
|
<div>
|
||||||
<strong className="flex items-center">
|
<strong className="flex items-center">
|
||||||
Version 1.1.0 (August 24rd 2024)
|
Version 1.1.0 (August 24rd 2024)
|
||||||
614
src/lib/api.ts
614
src/lib/api.ts
@ -6,399 +6,425 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
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}`;
|
||||||
|
|
||||||
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 }>> {
|
||||||
const result = await fetch(connector("/motd", { version: 1 }), {
|
const result = await fetch(connector("/motd", { version: 1 }), {
|
||||||
body: JSON.stringify({ motd: list }),
|
body: JSON.stringify({ motd: list }),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
let json = await result.json();
|
let json = await result.json();
|
||||||
return json.result;
|
return json.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCommunityServerFavorites(
|
export async function getCommunityServerFavorites(
|
||||||
server: string
|
server: string,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
const result = await fetch(
|
const result = await fetch(
|
||||||
connector(`/favorites/${server}/community-favorites`, { version: 0 }),
|
connector(`/favorites/${server}/community-favorites`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let json = await result.json();
|
let json = await result.json();
|
||||||
return json.result;
|
return json.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** requires authentication */
|
/** requires authentication */
|
||||||
export async function favoriteServer(server: string) {
|
export async function favoriteServer(server: string) {
|
||||||
try {
|
try {
|
||||||
await fetch(
|
await fetch(
|
||||||
connector(`/favorites/${server}/favorite-server`, { version: 0 }),
|
connector(`/favorites/${server}/favorite-server`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Not authenticated with a user.");
|
throw Error("Not authenticated with a user.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** requires authentication */
|
/** requires authentication */
|
||||||
export async function isFavorited(server: string): Promise<boolean> {
|
export async function isFavorited(server: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/favorites/${server}/favorited`, { version: 0 }),
|
connector(`/favorites/${server}/favorited`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return (await response.json()).result;
|
return (await response.json()).result;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Not authenticated with a user.");
|
throw Error("Not authenticated with a user.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** requires authentication */
|
/** requires authentication */
|
||||||
export async function getAccountFavorites(): Promise<Array<string>> {
|
export async function getAccountFavorites(): Promise<Array<string>> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/favorites/account-favorites`, { version: 0 }),
|
connector(`/favorites/account-favorites`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return (await response.json()).result;
|
return (await response.json()).result;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Not authenticated with a user.");
|
throw Error("Not authenticated with a user.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* currently not used in frontend yet
|
* currently not used in frontend yet
|
||||||
*/
|
*/
|
||||||
export async function getHistoricalData(
|
export async function getHistoricalData(
|
||||||
server: string,
|
server: string,
|
||||||
scopes: Array<"player_count" | "favorites" | "server" | "time">
|
scopes: Array<"player_count" | "favorites" | "server" | "time">,
|
||||||
): Promise<
|
): Promise<
|
||||||
Array<{
|
Array<{
|
||||||
player_count?: number;
|
player_count?: number;
|
||||||
favorites?: number;
|
favorites?: number;
|
||||||
server?: string;
|
server?: string;
|
||||||
time?: number;
|
time?: number;
|
||||||
}>
|
}>
|
||||||
> {
|
> {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/history/${server}/get-historical-data`, { version: 0 }),
|
connector(`/history/${server}/get-historical-data`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ scopes }),
|
body: JSON.stringify({ scopes }),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return (await response.json()).data;
|
return (await response.json()).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getShortTermData(
|
export async function getShortTermData(
|
||||||
server: string,
|
server: string,
|
||||||
scopes: Array<"player_count" | "favorites" | "server" | "date">
|
scopes: Array<"player_count" | "favorites" | "server" | "date">,
|
||||||
): Promise<
|
): Promise<
|
||||||
Array<{
|
Array<{
|
||||||
player_count?: number;
|
player_count?: number;
|
||||||
favorites?: number;
|
favorites?: number;
|
||||||
server?: string;
|
server?: string;
|
||||||
time?: number;
|
time?: number;
|
||||||
}>
|
}>
|
||||||
> {
|
> {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/history/${server}/get-short-term-data`, { version: 0 }),
|
connector(`/history/${server}/get-short-term-data`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ scopes }),
|
body: JSON.stringify({ scopes }),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return (await response.json()).data;
|
return (await response.json()).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMetaShortTerm(
|
export async function getMetaShortTerm(
|
||||||
scopes: Array<"total_players" | "total_servers" | "date">
|
scopes: Array<"total_players" | "total_servers" | "date">,
|
||||||
): Promise<
|
): Promise<
|
||||||
Array<{
|
Array<{
|
||||||
total_players?: number;
|
total_players?: number;
|
||||||
total_servers?: number;
|
total_servers?: number;
|
||||||
unix?: number;
|
unix?: number;
|
||||||
}>
|
}>
|
||||||
> {
|
> {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/history/meta-short-term-data`, { version: 0 }),
|
connector(`/history/meta-short-term-data`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: JSON.stringify({ scopes }),
|
body: JSON.stringify({ scopes }),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return (await response.json()).data;
|
return (await response.json()).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** requires authentication */
|
/** requires authentication */
|
||||||
export async function linkMCAccount(code: string): Promise<string | undefined> {
|
export async function linkMCAccount(code: string): Promise<string | undefined> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/account-linking/claim-account-code`, { version: 0 }),
|
connector(`/account-linking/claim-account-code`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ code }),
|
body: JSON.stringify({ code }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status == 400) {
|
if (response.status == 400) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await response.json()).player;
|
return (await response.json()).player;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Incorrect code");
|
throw Error("Incorrect code");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** requires authentication */
|
/** requires authentication */
|
||||||
export async function unlinkMCAccount(): Promise<boolean> {
|
export async function unlinkMCAccount(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/account-linking/unlink-account`, { version: 0 }),
|
connector(`/account-linking/unlink-account`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error(
|
throw Error(
|
||||||
"Not authenticated with a user or user already linked account."
|
"Not authenticated with a user or user already linked account.",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function serverOwned(server: string): Promise<boolean> {
|
export async function serverOwned(server: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/account-linking/is-owned`, { version: 0 }),
|
connector(`/account-linking/is-owned`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ server }),
|
body: JSON.stringify({ server }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return (await response.json()).owned;
|
return (await response.json()).owned;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Error while running API");
|
throw Error("Error while running API");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** requires authentication */
|
/** requires authentication */
|
||||||
export async function userOwnedServer(server: string): Promise<boolean> {
|
export async function userOwnedServer(server: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/account-linking/owned-user`, { version: 0 }),
|
connector(`/account-linking/owned-user`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ server }),
|
body: JSON.stringify({ server }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return (await response.json()).result;
|
return (await response.json()).result;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Error while running API");
|
throw Error("Error while running API");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** requires authentication */
|
/** requires authentication */
|
||||||
export async function ownServer(server: string): Promise<boolean> {
|
export async function ownServer(server: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/account-linking/own-server`, { version: 0 }),
|
connector(`/account-linking/own-server`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ server }),
|
body: JSON.stringify({ server }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status >= 400) {
|
if (response.status >= 400) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Error while running API");
|
throw Error("Error while running API");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** requires authentication */
|
/** requires authentication */
|
||||||
export async function unownServer(server: string): Promise<boolean> {
|
export async function unownServer(server: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/account-linking/unown-server`, { version: 0 }),
|
connector(`/account-linking/unown-server`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ server }),
|
body: JSON.stringify({ server }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status == 400) {
|
if (response.status == 400) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Error while running API");
|
throw Error("Error while running API");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** requires authentication */
|
/** requires authentication */
|
||||||
export async function setCustomization(
|
export async function setCustomization(
|
||||||
server: string,
|
server: string,
|
||||||
customization: any
|
customization: any,
|
||||||
): Promise<boolean | Error> {
|
): Promise<boolean | Error> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/customization/${server}/set`, { version: 0 }),
|
connector(`/customization/${server}/set`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ customization }),
|
body: JSON.stringify({ customization }),
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status == 400) {
|
if (response.status == 400) {
|
||||||
throw Error("Error while running API");
|
throw Error("Error while running API");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Error while running API");
|
throw Error("Error while running API");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getCustomization(server: string): Promise<any> {
|
export async function getCustomization(server: string): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/customization/${server}/get`, { version: 0 }),
|
connector(`/customization/${server}/get`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status == 400) {
|
if (response.status == 400) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await response.json()).results;
|
return (await response.json()).results;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Error while running API");
|
throw Error("Error while running API");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sortedFavorites(): Promise<
|
export async function sortedFavorites(): Promise<
|
||||||
Array<{ server: string; favorites: number }> | boolean
|
Array<{ server: string; favorites: number }> | boolean
|
||||||
> {
|
> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
connector(`/sorting/favorites`, { version: 0 }),
|
connector(`/sorting/favorites`, { version: 0 }),
|
||||||
{
|
{
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response.status == 400) {
|
if (response.status == 400) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (await response.json()).results;
|
return (await response.json()).results;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Error while running API");
|
throw Error("Error while running API");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function reportServer(
|
export async function reportServer(
|
||||||
server: string,
|
server: string,
|
||||||
reason: string
|
reason: string,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(connector(`/report-server`, { version: 1 }), {
|
const response = await fetch(connector(`/report-server`, { version: 1 }), {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ server, reason }),
|
body: JSON.stringify({ server, reason }),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (response.status == 400) {
|
if (response.status == 400) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch {
|
} catch {
|
||||||
throw Error("Error while running API");
|
throw Error("Error while running API");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function setAccountSL(
|
||||||
|
data: number | boolean | null,
|
||||||
|
type: "srv" | "ipr" | "pad",
|
||||||
|
): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
connector(`/account-sl/change`, { version: 0 }),
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ data, type }),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 400) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
throw Error("Error while running API");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
src/pages/api/v0/account-sl/change.ts
Normal file
47
src/pages/api/v0/account-sl/change.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { clerkClient, getAuth } from "@clerk/nextjs/server";
|
||||||
|
|
||||||
|
export default async function handler(
|
||||||
|
req: NextApiRequest,
|
||||||
|
res: NextApiResponse,
|
||||||
|
) {
|
||||||
|
const { userId } = getAuth(req);
|
||||||
|
|
||||||
|
if (!userId) {
|
||||||
|
return res.status(401).json({ error: "Unauthorized" });
|
||||||
|
}
|
||||||
|
const { data } = req.body;
|
||||||
|
|
||||||
|
if (data === undefined) {
|
||||||
|
res.status(400).send({ message: "Couldn't find data" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { type } = req.body;
|
||||||
|
|
||||||
|
if (type === undefined) {
|
||||||
|
res.status(400).send({ message: "Couldn't find data" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (data === null) {
|
||||||
|
clerkClient.users.updateUserMetadata(userId, {
|
||||||
|
publicMetadata: { [type]: null },
|
||||||
|
});
|
||||||
|
res.status(200).send({ message: "Success" });
|
||||||
|
}
|
||||||
|
if (type !== "srv" && type !== "ipr" && type !== "pad")
|
||||||
|
return res.status(400).send({ message: "Couldn't find data" });
|
||||||
|
|
||||||
|
if (type === "srv" && typeof data !== "boolean")
|
||||||
|
return res.status(400).send({ message: "Couldn't find data" });
|
||||||
|
|
||||||
|
if (type === "ipr" && typeof data !== "number")
|
||||||
|
return res.status(400).send({ message: "Couldn't find data" });
|
||||||
|
|
||||||
|
if (type === "pad" && typeof data !== "number")
|
||||||
|
return res.status(400).send({ message: "Couldn't find data" });
|
||||||
|
|
||||||
|
clerkClient.users.updateUserMetadata(userId, {
|
||||||
|
publicMetadata: { [type]: typeof data === "number" ? data.toString() : data },
|
||||||
|
});
|
||||||
|
res.status(200).send({ message: "Success" });
|
||||||
|
}
|
||||||
@ -18,9 +18,16 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"contentlayer/generated": ["./.contentlayer/generated"],
|
||||||
"@/*": ["./src/*"]
|
"@/*": ["./src/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
".contentlayer/generated"
|
||||||
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user