add super basic settings page
All checks were successful
Deploy / deploy (push) Successful in 4m48s

This commit is contained in:
Lee 2024-10-01 21:36:46 +01:00
parent 5e84e553a1
commit 16f3284110
8 changed files with 150 additions and 27 deletions

@ -1,5 +1,3 @@
"use client"; export default function HomePage() {
export default function Home() {
return <main>hi</main>; return <main>hi</main>;
} }

@ -0,0 +1,16 @@
import Card from "@/components/card";
import Settings from "@/components/settings/settings";
export default function SettingsPage() {
return (
<main className="w-full">
<Card className="w-full gap-4">
<div>
<p className="font-semibold">Settings</p>
<p>Configure settings your ScoreSaber Reloaded settings!</p>
</div>
<Settings />
</Card>
</main>
);
}

@ -46,10 +46,7 @@ export default class Database extends Dexie {
* Populates the default settings * Populates the default settings
*/ */
async populateDefaults() { async populateDefaults() {
await this.settings.add({ await this.resetSettings();
id: SETTINGS_ID, // Fixed ID for the single settings object
backgroundImage: "/assets/background.jpg",
});
} }
/** /**
@ -70,6 +67,19 @@ export default class Database extends Dexie {
async setSettings(settings: Settings) { async setSettings(settings: Settings) {
return this.settings.update(SETTINGS_ID, settings); return this.settings.update(SETTINGS_ID, settings);
} }
/**
* Resets the settings in the database
*/
async resetSettings() {
this.settings.delete(SETTINGS_ID);
this.settings.add({
id: SETTINGS_ID, // Fixed ID for the single settings object
backgroundCover: "/assets/background.jpg",
});
return this.getSettings();
}
} }
export const db = new Database(); export const db = new Database();

@ -16,9 +16,9 @@ export default class Settings extends Entity<Database> {
playerId?: string; playerId?: string;
/** /**
* The background image to use * The background image or color to use
*/ */
backgroundImage?: string; backgroundCover?: string;
/** /**
* Sets the players id * Sets the players id
@ -36,7 +36,7 @@ export default class Settings extends Entity<Database> {
* @param image the new background image * @param image the new background image
*/ */
public setBackgroundImage(image: string) { public setBackgroundImage(image: string) {
this.backgroundImage = image; this.backgroundCover = image;
this.db.setSettings(this); this.db.setSettings(this);
} }
} }

@ -9,28 +9,28 @@ export default function BackgroundCover() {
const database = useDatabase(); const database = useDatabase();
const settings = useLiveQuery(() => database.getSettings()); const settings = useLiveQuery(() => database.getSettings());
if (settings == undefined || settings?.backgroundImage == undefined || settings?.backgroundImage == "") { if (settings == undefined || settings?.backgroundCover == undefined || settings?.backgroundCover == "") {
return null; // Don't render anything if the background image is not set return null; // Don't render anything if the background image is not set
} }
let backgroundImage = settings.backgroundImage; let backgroundCover = settings.backgroundCover;
let prependWebsiteUrl = false; let prependWebsiteUrl = false;
// Remove the prepending slash // Remove the prepending slash
if (backgroundImage.startsWith("/")) { if (backgroundCover.startsWith("/")) {
prependWebsiteUrl = true; prependWebsiteUrl = true;
backgroundImage = backgroundImage.substring(1); backgroundCover = backgroundCover.substring(1);
} }
if (prependWebsiteUrl) { if (prependWebsiteUrl) {
backgroundImage = config.siteUrl + "/" + backgroundImage; backgroundCover = config.siteUrl + "/" + backgroundCover;
} }
// Static background color // Static background color
if (backgroundImage.startsWith("#")) { if (backgroundCover.startsWith("#")) {
return ( return (
<div <div
className={`fixed -z-50 object-cover w-screen h-screen pointer-events-none select-none`} className={`fixed -z-50 object-cover w-screen h-screen pointer-events-none select-none`}
style={{ backgroundColor: backgroundImage }} style={{ backgroundColor: backgroundCover }}
/> />
); );
} }
@ -38,7 +38,7 @@ export default function BackgroundCover() {
return ( return (
// eslint-disable-next-line @next/next/no-img-element // eslint-disable-next-line @next/next/no-img-element
<img <img
src={getImageUrl(backgroundImage)} src={getImageUrl(backgroundCover)}
alt="Background image" alt="Background image"
fetchPriority="high" fetchPriority="high"
className={`fixed -z-50 object-cover w-screen h-screen blur-sm brightness-[33%] pointer-events-none select-none`} className={`fixed -z-50 object-cover w-screen h-screen blur-sm brightness-[33%] pointer-events-none select-none`}

@ -1,4 +1,4 @@
import { HomeIcon } from "@heroicons/react/24/solid"; import { CogIcon, HomeIcon } from "@heroicons/react/24/solid";
import { MagnifyingGlassIcon } from "@radix-ui/react-icons"; import { MagnifyingGlassIcon } from "@radix-ui/react-icons";
import Link from "next/link"; import Link from "next/link";
import React from "react"; import React from "react";
@ -8,6 +8,7 @@ import ProfileButton from "./profile-button";
type NavbarItem = { type NavbarItem = {
name: string; name: string;
link: string; link: string;
align: "left" | "right";
icon: React.ReactNode; icon: React.ReactNode;
}; };
@ -15,12 +16,20 @@ const items: NavbarItem[] = [
{ {
name: "Home", name: "Home",
link: "/", link: "/",
icon: <HomeIcon className="h-5 w-5" />, // Add your home icon here align: "left",
icon: <HomeIcon className="h-5 w-5" />,
}, },
{ {
name: "Search", name: "Search",
link: "/search", link: "/search",
icon: <MagnifyingGlassIcon className="h-5 w-5" />, // Add your search icon here align: "right",
icon: <MagnifyingGlassIcon className="h-5 w-5" />,
},
{
name: "Settings",
link: "/settings",
align: "right",
icon: <CogIcon className="h-5 w-5" />,
}, },
]; ];
@ -33,7 +42,8 @@ const renderNavbarItem = (item: NavbarItem) => (
); );
export default function Navbar() { export default function Navbar() {
const rightItem = items[items.length - 1]; const rightItems = items.filter(item => item.align === "right");
const leftItems = items.filter(item => item.align === "left");
return ( return (
<div className="w-full sticky top-0 z-[999]"> <div className="w-full sticky top-0 z-[999]">
@ -42,17 +52,21 @@ export default function Navbar() {
<div className="flex items-center h-full"> <div className="flex items-center h-full">
<ProfileButton /> <ProfileButton />
{items.slice(0, -1).map((item, index) => ( {leftItems.map((item, index) => (
<Link href={item.link} key={index} className="h-full"> <Link href={item.link} key={index} className="h-full">
<NavbarButton key={index}>{renderNavbarItem(item)}</NavbarButton> <NavbarButton key={index}>{renderNavbarItem(item)}</NavbarButton>
</Link> </Link>
))} ))}
</div> </div>
{/* Right-aligned item */} {/* Right-aligned items */}
<Link href={rightItem.link} className="h-full"> <div className="flex items-center h-full">
<NavbarButton>{renderNavbarItem(rightItem)}</NavbarButton> {rightItems.map((item, index) => (
<Link href={item.link} key={index} className="h-full">
<NavbarButton key={index}>{renderNavbarItem(item)}</NavbarButton>
</Link> </Link>
))}
</div>
</div> </div>
</div> </div>
); );

@ -0,0 +1,84 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { Button } from "../ui/button";
import { Form, FormControl, FormField, FormItem, FormLabel } from "../ui/form";
import { Input } from "../ui/input";
import useDatabase from "@/hooks/use-database";
import { useLiveQuery } from "dexie-react-hooks";
import { useEffect } from "react";
import { useToast } from "@/hooks/use-toast";
const formSchema = z.object({
backgroundCover: z.string().min(4).max(128),
});
export default function Settings() {
const database = useDatabase();
const settings = useLiveQuery(() => database.getSettings());
const { toast } = useToast();
const form = useForm<z.infer<typeof formSchema>>({
resolver: zodResolver(formSchema),
defaultValues: {
// They will get overwritten later...
backgroundCover: "",
},
});
/**
* Handles the form submission
*
* @param backgroundCover the new background cover
*/
async function onSubmit({ backgroundCover }: z.infer<typeof formSchema>) {
if (!settings) {
return;
}
settings.backgroundCover = backgroundCover;
await database.setSettings(settings);
toast({
title: "Settings saved",
description: "Your settings have been saved.",
variant: "success",
});
}
/**
* Handle setting the default form values.
*/
useEffect(() => {
form.setValue("backgroundCover", settings?.backgroundCover || "");
}, [settings]);
return (
<div className="flex flex-col gap-3">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-2">
{/* Background Cover */}
<FormField
control={form.control}
name="backgroundCover"
render={({ field }) => (
<FormItem>
<FormLabel>Background Cover</FormLabel>
<FormControl>
<Input className="w-full sm:w-72 text-sm" placeholder="Hex or URL..." {...field} />
</FormControl>
</FormItem>
)}
/>
{/* Saving Settings */}
<Button type="submit" className="w-fit">
Save
</Button>
</form>
</Form>
</div>
);
}

@ -31,6 +31,7 @@ const toastVariants = cva(
variant: { variant: {
default: "border bg-secondary text-foreground", default: "border bg-secondary text-foreground",
destructive: "destructive group border-destructive bg-destructive text-destructive-foreground", destructive: "destructive group border-destructive bg-destructive text-destructive-foreground",
success: "border bg-green-600 text-foreground",
}, },
}, },
defaultVariants: { defaultVariants: {