feat: add settings, minor features

This commit is contained in:
dvelo 2025-02-18 18:19:04 -06:00
parent 086ccdc3ee
commit a0a139de02
11 changed files with 237 additions and 95 deletions

@ -31,6 +31,7 @@
"@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-select": "2.1.2", "@radix-ui/react-select": "2.1.2",
"@radix-ui/react-switch": "1.1.0", "@radix-ui/react-switch": "1.1.0",
"@radix-ui/react-tabs": "^1.1.3",
"@types/react": "^19.0.8", "@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3", "@types/react-dom": "^19.0.3",
"@unocss/eslint-plugin": "^0.61.5", "@unocss/eslint-plugin": "^0.61.5",

@ -0,0 +1,16 @@
import { Settings } from "@/components/feat/settings/settings";
import type { Metadata } from "next";
export const metadata: Metadata = {
applicationName: "MHSF",
title: "Settings · MHSF",
description: "View settings for MHSF",
};
export default function ServerListPage() {
return (
<div>
<Settings />
</div>
);
}

@ -1,9 +1,14 @@
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { import {
DropdownMenuItem, DropdownMenuItem,
DropdownMenuSeparator, DropdownMenuSeparator,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import Github from "@/components/ui/github";
import { Link } from "@/components/util/link";
import { version } from "@/config/version";
import { SignedIn, SignedOut, useClerk } from "@clerk/nextjs"; import { SignedIn, SignedOut, useClerk } from "@clerk/nextjs";
import { LogIn, Settings, Ship, User, UserCog } from "lucide-react"; import { LogIn, LogOut, Settings, Ship, User, UserCog } from "lucide-react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
export function MenuDropdown() { export function MenuDropdown() {
@ -51,14 +56,44 @@ export function MenuDropdown() {
User Settings User Settings
</span> </span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => clerk.signOut()}>
<span className="flex items-center gap-2 text-red-500">
<LogOut size={16} />
Sign out
</span>
</DropdownMenuItem>
</SignedIn> </SignedIn>
<DropdownMenuSeparator>App</DropdownMenuSeparator> <DropdownMenuSeparator>App</DropdownMenuSeparator>
<Link href="/settings">
<DropdownMenuItem> <DropdownMenuItem>
<span className="flex items-center gap-2"> <span className="flex items-center gap-2">
<Settings size={16} /> <Settings size={16} />
Settings Settings
</span> </span>
</DropdownMenuItem> </DropdownMenuItem>
</Link>
<DropdownMenuSeparator />
<li className="flex flex-col px-2 py-1 mx-auto my-1 text-xs w-full">
<div className="flex flex-row gap-2 w-full items-center">
<div className="flex-1">
<button
className="hover:brightness-110 transition-all"
type="button"
>
<Badge variant="blue-subtle">v{version}</Badge>
</button>
</div>
<Link href="Special:GitHub">
<Button
variant="tertiary"
className="flex items-center"
size="square-lg"
>
<Github />
</Button>
</Link>
</div>
</li>
</> </>
); );
} }

@ -14,12 +14,11 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import Github from "@/components/ui/github"; import Github from "@/components/ui/github";
import { ALegacy } from "@/components/util/link"; import { Link } from "@/components/util/link";
import { version } from "@/config/version"; import { version } from "@/config/version";
import { useScroll } from "@/lib/hooks/use-scroll"; import { useScroll } from "@/lib/hooks/use-scroll";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { Menu, ServerCrash } from "lucide-react"; import { Menu, ServerCrash } from "lucide-react";
import Link from "next/link";
import { MenuDropdown } from "./menu-dropdown"; import { MenuDropdown } from "./menu-dropdown";
export function NavBar() { export function NavBar() {
@ -47,28 +46,28 @@ export function NavBar() {
</Link> </Link>
</ContextMenuTrigger> </ContextMenuTrigger>
<ContextMenuContent className="overflow-hidden min-w-[300px]"> <ContextMenuContent className="overflow-hidden min-w-[300px]">
<ALegacy href="Special:Root"> <Link href="Special:Root">
<ContextMenuItem> <ContextMenuItem>
<span className="pl-2 flex gap-2 items-center"> <span className="pl-2 flex gap-2 items-center">
<ServerCrash size={16} /> Go home <ServerCrash size={16} /> Go home
</span> </span>
</ContextMenuItem> </ContextMenuItem>
</ALegacy> </Link>
<ContextMenuSeparator /> <ContextMenuSeparator />
<ALegacy href="Special:GitHub"> <Link href="Special:GitHub">
<ContextMenuItem> <ContextMenuItem>
<span className="pl-2 flex gap-2 items-center"> <span className="pl-2 flex gap-2 items-center">
<Github /> Open GitHub <Github /> Open GitHub
</span> </span>
</ContextMenuItem> </ContextMenuItem>
</ALegacy> </Link>
<ALegacy href="Special:GitHub/releases"> <Link href="Special:GitHub/releases">
<ContextMenuItem> <ContextMenuItem>
<span className="pl-2 flex gap-2 items-center"> <span className="pl-2 flex gap-2 items-center">
<Github /> Open GitHub Releases <Github /> Open GitHub Releases
</span> </span>
</ContextMenuItem> </ContextMenuItem>
</ALegacy> </Link>
</ContextMenuContent> </ContextMenuContent>
</ContextMenu> </ContextMenu>
</span> </span>

@ -0,0 +1,5 @@
import { Material } from "@/components/ui/material";
export function BrowserSettings() {
return <Material></Material>;
}

@ -0,0 +1,88 @@
"use client";
import { Button } from "@/components/ui/button";
import { Material } from "@/components/ui/material";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import { SignedIn, SignedOut, useClerk } from "@clerk/nextjs";
import { ExternalLink, Globe, TabletSmartphone } from "lucide-react";
import { BrowserSettings } from "./browser-settings";
export function Settings() {
const clerk = useClerk();
return (
<main className="lg:px-[10rem] px-4">
<h1 className="scroll-m-20 text-2xl font-extrabold tracking-tight lg:text-4xl mb-3">
Settings
</h1>
<Tabs defaultValue="account" className="mt-3">
<TabsList>
<TabsTrigger
value="browser-settings"
className="flex items-center gap-2"
>
<Globe size={16} />
Browser stored settings
</TabsTrigger>
<TabsTrigger
value="user-settings"
className="flex items-center gap-2"
>
<TabletSmartphone size={16} />
User stored settings
</TabsTrigger>
<SignedIn>
<TabsTrigger
value="account-settings"
className="flex items-center gap-2"
onClick={() => clerk.openUserProfile()}
>
Account Settings <ExternalLink size={16} />
</TabsTrigger>
</SignedIn>
</TabsList>
<TabsContent value="browser-settings">
<BrowserSettings />
</TabsContent>
<TabsContent value="user-settings">
<SignedOut>
<Material className="mt-6 grid gap-4">
<h3
className={cn(
"scroll-m-20 text-2xl font-semibold tracking-tight bg-gradient-to-r from-[#3b82f6] to-[#ef4444] bg-clip-text text-transparent",
"w-full flex items-center text-center justify-center"
)}
>
Create an account.
</h3>
<span className="w-full flex items-center text-center justify-center">
<p className="max-w-[600px] break-words">
Creating an account allows you to mark your favorite servers,
link your own servers with your own, link your Minecraft
account to your MHSF one, and store various different settings
to sync across all of the devices linked with your account.
</p>
</span>
<span className="flex items-center w-full justify-center gap-2">
<Button
className="max-w-[400px]"
onClick={() => clerk.openSignUp()}
>
Sign-up
</Button>
<Button
className="max-w-[400px]"
variant="secondary"
onClick={() => clerk.openSignIn()}
>
Sign-in
</Button>
</span>
</Material>
</SignedOut>
</TabsContent>
</Tabs>
</main>
);
}

@ -54,7 +54,7 @@ export interface BadgeProps
function Badge({ className, variant, ...props }: BadgeProps) { function Badge({ className, variant, ...props }: BadgeProps) {
return ( return (
<div className={cn(badgeVariants({ variant }), className)} {...props} /> <span className={cn(badgeVariants({ variant }), className)} {...props} />
); );
} }

@ -0,0 +1,61 @@
"use client";
import * as React from "react";
import * as TabsPrimitive from "@radix-ui/react-tabs";
import { cn } from "@/lib/utils";
const Tabs = TabsPrimitive.Root;
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"flex flex-row items-center gap-1 p-1 rounded-full bg-white/60 dark:bg-zinc-800/60 backdrop-blur-lg border border-slate-200/60 dark:border-zinc-800 shadow-lg border-opacity-50",
className
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
"font-medium rounded-full px-4 py-1 hover:bg-slate-200/40 hover:dark:bg-zinc-700/40 group transition-colors duration-100 relative z-0 flex-shrink-0",
className
)}
{...props}
>
{props.children}
<div
className="rounded-full bg-slate-100/60 dark:bg-zinc-700/60
absolute group-data-[state=inactive]:hidden inset-0 w-full h-full -z-10"
/>
</TabsPrimitive.Trigger>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
"mt-2 ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
className
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent };

@ -1,84 +1,22 @@
/* import { type LinkProps, default as NextLink } from "next/link";
* MHSF, Minehut Server List
* All external content is rather licensed under the ECA Agreement
* located here: https://list.mlnehut.com/docs/legal/external-content-agreement
*
* All code under MHSF is licensed under the MIT License
* by open source contributors
*
* Copyright (c) 2024 dvelo
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
import type { ReactNode } from "react";
import { default as NextLink } from "next/link";
import { Book, ExternalLink, NotebookText } from "lucide-react"; import { Book, ExternalLink, NotebookText } from "lucide-react";
export default function A({ export function Link(
children, props: LinkProps & {
alt, children?: React.ReactNode;
}: {
children: string;
alt: string | ReactNode;
}) {
return (
<NextLink
href={pageFind(children || "") || "#"}
className="transition duration-300 underline"
title={children}
>
{(children || "").startsWith("Docs:") && (
<Book size={16} className="mr-[2px] inline-flex" />
)}
{(children || "").startsWith("Wiki:") && (
<NotebookText size={14} className="mr-[2px] mb-[3px] inline-flex" />
)}
{alt}
{(children || "").startsWith("https") && (
<ExternalLink size={12} className="ml-[2px] mb-[3px] inline-flex" />
)}
</NextLink>
);
} }
) {
const href = props.href as string;
export function ALegacy({
children,
href,
}: {
children?: string | ReactNode;
href?: string;
}) {
return ( return (
<NextLink <NextLink {...props} href={pageFind(href || "") || "#"} title={href}>
href={pageFind(href || "") || "#"}
className="no-underline transition duration-300 "
title={href}
>
{(href || "").startsWith("Docs:") && ( {(href || "").startsWith("Docs:") && (
<Book size={16} className="mr-[2px] inline-flex" /> <Book size={16} className="mr-[2px] inline-flex" />
)} )}
{(href || "").startsWith("Wiki:") && ( {(href || "").startsWith("Wiki:") && (
<NotebookText size={14} className="mr-[2px] mb-[3px] inline-flex" /> <NotebookText size={14} className="mr-[2px] mb-[3px] inline-flex" />
)} )}
{children} {props.children}
{(href || "").startsWith("https") && ( {(href || "").startsWith("https") && (
<ExternalLink size={12} className="ml-[2px] mb-[3px] inline-flex" /> <ExternalLink size={12} className="ml-[2px] mb-[3px] inline-flex" />
)} )}

@ -29,10 +29,9 @@
*/ */
"use client"; "use client";
import A from "@/components/util/link"; import { Link } from "@/components/util/link";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import Github from "@/components/ui/github"; import Github from "@/components/ui/github";
import Link from "next/link";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
const User = ({ user }: { user: string }) => ( const User = ({ user }: { user: string }) => (
@ -238,9 +237,9 @@ export const changelog: { name: string; id: string; changelog: ReactNode }[] = [
<li> Minor backend changes</li> <li> Minor backend changes</li>
<li> <li>
{" "} {" "}
<A alt="Please check on GitHub for statuses about this project."> <Link href="Special:GitHub/releases/tag/1.3.2">
Special:GitHub/releases/tag/1.3.2 Please check on GitHub for statuses about this project.
</A> </Link>
</li> </li>
</ul> </ul>
</div> </div>
@ -256,11 +255,11 @@ export const changelog: { name: string; id: string; changelog: ReactNode }[] = [
</strong> </strong>
<ul> <ul>
<li> <li>
<A alt="New documentation linking">Docs:Reading</A> <Link href="Docs:Reading">New documentation linking</Link>
</li> </li>
<li> <li>
Achievements are here! See more at{" "} Achievements are here! See more at{" "}
<A alt="here">Docs:Advanced/Achievements</A> <Link href="Docs:Advanced/Achievements">here</Link>
</li> </li>
<li> Finally fixed Cron actions for the final time</li> <li> Finally fixed Cron actions for the final time</li>
<li> Overhauled account preferences</li> <li> Overhauled account preferences</li>

@ -2315,7 +2315,7 @@
"@radix-ui/react-use-previous" "1.1.0" "@radix-ui/react-use-previous" "1.1.0"
"@radix-ui/react-use-size" "1.1.0" "@radix-ui/react-use-size" "1.1.0"
"@radix-ui/react-tabs@^1.1.0": "@radix-ui/react-tabs@^1.1.0", "@radix-ui/react-tabs@^1.1.3":
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz#c47c8202dc676dea47676215863d2ef9b141c17a" resolved "https://registry.yarnpkg.com/@radix-ui/react-tabs/-/react-tabs-1.1.3.tgz#c47c8202dc676dea47676215863d2ef9b141c17a"
integrity sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng== integrity sha512-9mFyI30cuRDImbmFF6O2KUJdgEOsGh9Vmx9x/Dh9tOhL7BngmQPQfwW4aejKm5OHpfWIdmeV6ySyuxoOGjtNng==