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

View File

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

View File

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

View File

@ -46,10 +46,7 @@ export default class Database extends Dexie {
* Populates the default settings
*/
async populateDefaults() {
await this.settings.add({
id: SETTINGS_ID, // Fixed ID for the single settings object
backgroundImage: "/assets/background.jpg",
});
await this.resetSettings();
}
/**
@ -70,6 +67,19 @@ export default class Database extends Dexie {
async setSettings(settings: 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();

View File

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

View File

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

View File

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

View File

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

View File

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