feat: swap text field for monaco editor

This commit is contained in:
dvelo 2024-10-06 09:34:56 -05:00
parent 30a5bc5166
commit dd9104f2f3
3 changed files with 470 additions and 463 deletions

@ -8,6 +8,9 @@
"suspicious": { "suspicious": {
"noExplicitAny": "off", "noExplicitAny": "off",
"noDoubleEquals": "warn" "noDoubleEquals": "warn"
},
"complexity": {
"noForEach": "off"
} }
} }
} }

@ -29,489 +29,493 @@
*/ */
"use client"; "use client";
import { Button } from "./ui/button";
import { Textarea } from "./ui/textarea";
import { CheckIcon, X } from "lucide-react";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { import {
getCustomization, getCustomization,
ownServer, ownServer,
reportServer, reportServer,
setCustomization, serverOwned as sOFunc,
serverOwned as sOFunc, setCustomization,
unownServer, unownServer,
userOwnedServer, userOwnedServer,
} from "@/lib/api"; } from "@/lib/api";
import toast from "react-hot-toast";
import { SignedIn, SignedOut, useUser } from "@clerk/nextjs";
import { OnlineServer } from "@/lib/types/mh-server"; import { OnlineServer } from "@/lib/types/mh-server";
import Link from "next/link"; import { SignedIn, SignedOut, useUser } from "@clerk/nextjs";
import Setting from "./ui/setting";
import {
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
Form,
} from "./ui/form";
import { z } from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { CheckIcon, X } from "lucide-react";
import Link from "next/link";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import toast from "react-hot-toast";
import { z } from "zod";
import { Button } from "./ui/button";
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "./ui/form";
import Setting from "./ui/setting";
import { Textarea } from "./ui/textarea";
import "@/themes.css"; import "@/themes.css";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "./ui/card";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { themes } from "@/lib/themes"; import { themes } from "@/lib/themes";
import Editor from "@monaco-editor/react";
import { useTheme } from "next-themes"; import { useTheme } from "next-themes";
import { DiscordPopover } from "./misc/DiscordPopover";
import { Spinner } from "./ui/spinner";
import { BannerPopover } from "./misc/BannerPopover"; import { BannerPopover } from "./misc/BannerPopover";
import { DiscordPopover } from "./misc/DiscordPopover";
import { import {
Dialog, Card,
DialogContent, CardContent,
DialogDescription, CardDescription,
DialogHeader, CardFooter,
DialogTitle, CardHeader,
DialogTrigger, CardTitle,
} from "./ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "./ui/dialog"; } from "./ui/dialog";
import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { Spinner } from "./ui/spinner";
const formSchema = z.object({ const formSchema = z.object({
description: z description: z
.string() .string()
.min(2, { .min(2, {
message: "Description must be at least 2 characters.", message: "Description must be at least 2 characters.",
}) })
.max(1250, { .max(1250, {
message: "Description cannot be longer than 1250 characters.", message: "Description cannot be longer than 1250 characters.",
}), }),
}); });
export default function ServerCustomize({ export default function ServerCustomize({
server, server,
cs, cs,
setCS, setCS,
}: { }: {
server: string; server: string;
cs: string; cs: string;
setCS: Dispatch<SetStateAction<string>>; setCS: Dispatch<SetStateAction<string>>;
}) { }) {
const [serverOwned, setServerOwned] = useState(false); const [serverOwned, setServerOwned] = useState(false);
const [userOwned, setUserOwned] = useState(false); const [userOwned, setUserOwned] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [reason, setReason] = useState(""); const [reason, setReason] = useState("");
const [description, setDescription] = useState(""); const [description, setDescription] = useState("");
const [get, setGet] = useState<any>({}); const [get, setGet] = useState<any>({});
const [author, setAuthor] = useState<string | undefined>(""); const [author, setAuthor] = useState<string | undefined>("");
const [minehutOwned, setMinehutOwned] = useState(false); const [minehutOwned, setMinehutOwned] = useState(false);
const { resolvedTheme: mode } = useTheme(); const { resolvedTheme: mode } = useTheme();
const { user, isSignedIn } = useUser(); const { user, isSignedIn } = useUser();
useEffect(() => { useEffect(() => {
sOFunc(server).then((c) => { sOFunc(server).then((c) => {
setServerOwned(c); setServerOwned(c);
getCustomization(server).then((b) => { getCustomization(server).then((b) => {
setGet(b); setGet(b);
setDescription(b != null ? b.description : ""); setDescription(b != null ? b.description : "");
form.reset({ description: b != null ? b.description : "" }); form.reset({ description: b != null ? b.description : "" });
setCS(b != null ? b.colorScheme : "zinc"); setCS(b != null ? b.colorScheme : "zinc");
userOwnedServer(server).then((c) => { userOwnedServer(server).then((c) => {
setUserOwned(c); setUserOwned(c);
fetch("https://api.minehut.com/servers").then((c) => { fetch("https://api.minehut.com/servers").then((c) => {
c.json().then((c: { servers: Array<OnlineServer> }) => { c.json().then((c: { servers: Array<OnlineServer> }) => {
c.servers.forEach((v) => { c.servers.forEach((v) => {
setAuthor(v.author); setAuthor(v.author);
if (v.name == server && isSignedIn) { if (v.name == server && isSignedIn) {
if (user?.publicMetadata.player === v.author) { if (user?.publicMetadata.player === v.author) {
setMinehutOwned(true); setMinehutOwned(true);
} }
} }
setLoading(false); setLoading(false);
}); });
}); });
}); });
}); });
}); });
}); });
}, [isSignedIn]); }, [isSignedIn]);
const form = useForm<z.infer<typeof formSchema>>({ const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
description, description,
}, },
}); });
if (loading) { if (loading) {
return ( return (
<> <>
<Spinner className="flex items-center" /> <Spinner className="flex items-center" />
<br /> <br />
</> </>
); );
} }
return ( return (
<> <>
<SignedOut> <SignedOut>
{!serverOwned && ( {!serverOwned && (
<div> <div>
<div className="font-bold">Do you own this server? </div> <div className="font-bold">Do you own this server? </div>
Create an account and link it to the owner of the server to Create an account and link it to the owner of the server to
customize it. customize it.
</div> </div>
)} )}
</SignedOut> </SignedOut>
<SignedIn> <SignedIn>
{!serverOwned && user?.publicMetadata.player == null && ( {!serverOwned && user?.publicMetadata.player == null && (
<div> <div>
<div className="font-bold">Do you own this server? </div> <div className="font-bold">Do you own this server? </div>
Create an account and link it to the owner of the server to Create an account and link it to the owner of the server to
customize it. customize it.
</div> </div>
)} )}
{serverOwned && !userOwned && ( {serverOwned && !userOwned && (
<div> <div>
<div className="font-bold"> <div className="font-bold">
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="/docs/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
server. server.
<Dialog> <Dialog>
<DialogTrigger> <DialogTrigger>
<Button className="h-[30px] ml-2">Report</Button> <Button className="h-[30px] ml-2">Report</Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
<DialogTitle>Report Server</DialogTitle> <DialogTitle>Report Server</DialogTitle>
<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="/docs/legal/external-content-agreement"> <Link href="/docs/legal/external-content-agreement">
ECA ECA
</Link>{" "} </Link>{" "}
to be a valid report. Typical response times include 1 hour to be a valid report. Typical response times include 1 hour
to 1 day, and you will not be notified if your report is 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
do, your account will be banned. We are not Minehut do, your account will be banned. We are not Minehut
support, we cannot help you with a problem within the support, we cannot help you with a problem within the
Minehut platform or within the server, we can only Minehut platform or within the server, we can only
moderate the customization of the server. moderate the customization of the server.
</b>{" "} </b>{" "}
(if the problem is within the server,{" "} (if the problem is within the server,{" "}
<Link href="https://support.minehut.com/hc/en-us/requests/new?tf_subject=Reporting%20Server&tf_27062997154195=reports_appeals&tf_27063229498259=report_server"> <Link href="https://support.minehut.com/hc/en-us/requests/new?tf_subject=Reporting%20Server&tf_27062997154195=reports_appeals&tf_27063229498259=report_server">
report it on Minehut report it on Minehut
</Link> </Link>
)<br /> )<br />
<br /> <br />
<b>Reason:</b> <b>Reason:</b>
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<Textarea <Textarea
value={reason} value={reason}
onChange={(e) => setReason(e.target.value)} onChange={(e) => setReason(e.target.value)}
/> />
<div className="grid grid-flow-row gap-2"> <div className="grid grid-flow-row gap-2">
<Button <Button
onClick={async () => { onClick={async () => {
await toast.promise( await toast.promise(
new Promise(async (g, b) => { new Promise(async (g, b) => {
(await reportServer(server, reason)) ? g("") : b(); (await reportServer(server, reason)) ? g("") : b();
}), }),
{ {
success: "Report sent!", success: "Report sent!",
loading: "Sending report...", loading: "Sending report...",
error: "Error while sending report", error: "Error while sending report",
} },
); );
}} }}
> >
Report Server Report Server
</Button> </Button>
</div> </div>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<br /> <br />
</div> </div>
)} )}
</SignedIn> </SignedIn>
{!serverOwned && minehutOwned && ( {!serverOwned && minehutOwned && (
<div className="font-bold"> <div className="font-bold">
Do you own this server?{" "} Do you own this server?{" "}
<Button <Button
className="h-[30px] ml-2" className="h-[30px] ml-2"
onClick={async () => { onClick={async () => {
await toast.promise( await toast.promise(
new Promise(async (g, b) => { new Promise(async (g, b) => {
(await ownServer(server)) ? g("") : b(); (await ownServer(server)) ? g("") : b();
}), }),
{ {
success: "Owned server!", success: "Owned server!",
loading: "Owning server...", loading: "Owning server...",
error: "Error while owning server", error: "Error while owning server",
} },
); );
setMinehutOwned(true); setMinehutOwned(true);
setUserOwned(true); setUserOwned(true);
setServerOwned(true); setServerOwned(true);
}} }}
> >
Click to own this server Click to own this server
</Button> </Button>
</div> </div>
)} )}
<br /> <br />
{!userOwned && ( {!userOwned && (
<> <>
<strong> <strong>
{" "} {" "}
<X size={24} /> <X size={24} />
Whoops.. something went wrong Whoops.. something went wrong
</strong> </strong>
<div> You don't have permission to customize this server. </div> <div> You don't have permission to customize this server. </div>
</> </>
)} )}
{serverOwned && userOwned && ( {serverOwned && userOwned && (
<div className="max-w-[800px]"> <div className="max-w-[800px]">
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle>Description</CardTitle> <CardTitle>Description</CardTitle>
<CardDescription> <CardDescription>
Edit the description of the server. Edit the description of the server.
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<Form {...form}> <Form {...form}>
<form <form
onSubmit={form.handleSubmit((c) => { onSubmit={form.handleSubmit((c) => {
toast.promise(setCustomization(server, c), { toast.promise(setCustomization(server, c), {
success: "Successfully set description", success: "Successfully set description",
loading: "Setting description..", loading: "Setting description..",
error: "Error while setting descript", error: "Error while setting descript",
}); });
})} })}
> >
<CardContent> <CardContent>
<FormField <FormField
control={form.control} control={form.control}
name="description" name="description"
defaultValue={description} render={({ field }) => (
render={({ field }) => ( <FormItem>
<FormItem> <FormLabel>Description</FormLabel>
<FormLabel>Description</FormLabel> <FormControl>
<FormControl> <Editor
<Textarea height="90vh"
placeholder="Markdown is _supported_ in server descriptions" className="rounded"
{...field} defaultLanguage="markdown"
/> defaultValue=""
</FormControl> theme={mode == "dark" ? "vs-dark" : "light"}
<FormDescription> {...field}
Set the description for your server page. <br /> />
<small> </FormControl>
By adding a description, all text is subject to{" "} <FormDescription>
<Link href="https://minehut.wiki.gg/wiki/Rules"> Set the description for your server page. <br />
Minehuts Terms of Service <small>
</Link>{" "} By adding a description, all text is subject to{" "}
& the{" "} <Link href="https://minehut.wiki.gg/wiki/Rules">
<Link href="/docs/legal/external-content-agreement"> Minehuts Terms of Service
External Content Agreement </Link>{" "}
</Link> & the{" "}
. <Link href="/docs/legal/external-content-agreement">
</small> External Content Agreement
</FormDescription> </Link>
<FormMessage /> .
</FormItem> </small>
)} </FormDescription>
/> <FormMessage />
</CardContent> </FormItem>
<CardFooter className="border-t px-6 py-4"> )}
<Button type="submit" className="h-[30px]"> />
Edit Description </CardContent>
</Button> <CardFooter className="border-t px-6 py-4">
</CardFooter> <Button type="submit" className="h-[30px]">
</form> Edit Description
</Form> </Button>
</Card> </CardFooter>
<br /> </form>
<br /> </Form>
<Setting </Card>
name="Color Scheme" <br />
description={ <br />
<> <Setting
Pick any color scheme for the components on your server page to name="Color Scheme"
use. description={
</> <>
} Pick any color scheme for the components on your server page to
button={ use.
<Popover> </>
<PopoverTrigger> }
<Button type="submit" className="h-[30px]"> button={
Edit Color Schemes <Popover>
</Button> <PopoverTrigger>
</PopoverTrigger> <Button type="submit" className="h-[30px]">
<PopoverContent className="grid grid-cols-3 gap-1.5 w-[400px]"> Edit Color Schemes
{themes.map((colorObj) => { </Button>
const color = colorObj.name; </PopoverTrigger>
const theme = themes.find((theme) => theme.name === color); <PopoverContent className="grid grid-cols-3 gap-1.5 w-[400px]">
const isActive = cs === color; {themes.map((colorObj) => {
const color = colorObj.name;
const theme = themes.find((theme) => theme.name === color);
const isActive = cs === color;
if (!theme) { if (!theme) {
return null; return null;
} }
return ( return (
<Button <Button
variant={"outline"} variant={"outline"}
size="sm" size="sm"
key={theme.name} key={theme.name}
onClick={() => { onClick={() => {
setCS(theme.name); setCS(theme.name);
setCustomization(server, { colorScheme: theme.name }); setCustomization(server, { colorScheme: theme.name });
}} }}
className={ className={
"justify-start " + "justify-start " +
(isActive && "border-2 border-primary") (isActive && "border-2 border-primary")
} }
style={ style={
{ {
"--theme-primary": `hsl(${ "--theme-primary": `hsl(${
theme?.activeColor[ theme?.activeColor[
mode === "dark" ? "dark" : "light" mode === "dark" ? "dark" : "light"
] ]
})`, })`,
} as React.CSSProperties } as React.CSSProperties
} }
> >
<span <span
className={ className={
"mr-1 flex h-5 w-5 shrink-0 -translate-x-1 items-center justify-center rounded-full bg-[--theme-primary]" "mr-1 flex h-5 w-5 shrink-0 -translate-x-1 items-center justify-center rounded-full bg-[--theme-primary]"
} }
> >
{isActive && ( {isActive && (
<CheckIcon className="h-4 w-4 text-white" /> <CheckIcon className="h-4 w-4 text-white" />
)} )}
</span> </span>
{theme.label} {theme.label}
</Button> </Button>
); );
})} })}
</PopoverContent> </PopoverContent>
</Popover> </Popover>
} }
/> />
<br /> <br />
<br /> <br />
<Setting <Setting
name="Banner" name="Banner"
description={ description={
<> <>
Banners appear on both the server list, and the server page.{" "} Banners appear on both the server list, and the server page.{" "}
<i> <i>
You will have to provide your own Imgur image for your image. You will have to provide your own Imgur image for your image.
</i> </i>
<br /> <br />
<small> <small>
By adding a banner, all images are subject to{" "} By adding a banner, all images are subject to{" "}
<Link href="https://minehut.wiki.gg/wiki/Rules"> <Link href="https://minehut.wiki.gg/wiki/Rules">
Minehuts Terms of Service Minehuts Terms of Service
</Link> </Link>
,{" "} ,{" "}
<Link href="https://imgur.com/tos"> <Link href="https://imgur.com/tos">
Imgurs Terms of Service Imgurs Terms of Service
</Link>{" "} </Link>{" "}
& the{" "} & the{" "}
<Link href="/docs/legal/external-content-agreement"> <Link href="/docs/legal/external-content-agreement">
External Content Agreement External Content Agreement
</Link> </Link>
. .
</small> </small>
</> </>
} }
button={ button={
<Popover> <Popover>
<PopoverTrigger> <PopoverTrigger>
<Button className="h-[30px]">Change Banner</Button> <Button className="h-[30px]">Change Banner</Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<BannerPopover server={server} get={get} /> <BannerPopover server={server} get={get} />
</PopoverContent> </PopoverContent>
</Popover> </Popover>
} }
/> />
<br /> <br />
<br /> <br />
<Setting <Setting
name="Discord Server" name="Discord Server"
description={ description={
<> <>
Associate a Discord server embed in your server page. Associate a Discord server embed in your server page.
<br /> <br />
<small> <small>
By adding a Discord Server, all servers are subject to{" "} By adding a Discord Server, all servers are subject to{" "}
<Link href="https://discord.com/terms/"> <Link href="https://discord.com/terms/">
Discords Terms of Service Discords Terms of Service
</Link>{" "} </Link>{" "}
& the{" "} & the{" "}
<Link href="/docs/legal/external-content-agreement"> <Link href="/docs/legal/external-content-agreement">
External Content Agreement External Content Agreement
</Link> </Link>
. .
</small> </small>
</> </>
} }
button={ button={
<Popover> <Popover>
<PopoverTrigger> <PopoverTrigger>
<Button className="h-[30px]">Change Discord</Button> <Button className="h-[30px]">Change Discord</Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent> <PopoverContent>
<DiscordPopover server={server} get={get} /> <DiscordPopover server={server} get={get} />
</PopoverContent> </PopoverContent>
</Popover> </Popover>
} }
/> />
<br /> <br />
<br /> <br />
<Setting <Setting
name="Un-own server" name="Un-own server"
description={ description={
<> <>
By unowning the server, you will revert all customizations on By unowning the server, you will revert all customizations on
the server and not be associated with the server. the server and not be associated with the server.
</> </>
} }
button={ button={
<Button <Button
className="h-[30px]" className="h-[30px]"
variant="destructive" variant="destructive"
onClick={() => onClick={() =>
toast.promise(unownServer(server), { toast.promise(unownServer(server), {
success: "Un-owned server!", success: "Un-owned server!",
loading: "Un-owning server...", loading: "Un-owning server...",
error: "Error while un-owning server.", error: "Error while un-owning server.",
}) })
} }
> >
Un-own Server Un-own Server
</Button> </Button>
} }
/> />
<br /> <br />
<br /> <br />
</div> </div>
)} )}
</> </>
); );
} }

@ -808,7 +808,7 @@
"@monaco-editor/react@^4.6.0": "@monaco-editor/react@^4.6.0":
version "4.6.0" version "4.6.0"
resolved "https://registry.npmjs.org/@monaco-editor/react/-/react-4.6.0.tgz" resolved "https://registry.yarnpkg.com/@monaco-editor/react/-/react-4.6.0.tgz#bcc68671e358a21c3814566b865a54b191e24119"
integrity sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw== integrity sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==
dependencies: dependencies:
"@monaco-editor/loader" "^1.4.0" "@monaco-editor/loader" "^1.4.0"