From 99a1b3b2642c37cefa7a9153d18666be82b15d7f Mon Sep 17 00:00:00 2001 From: Leo dev Date: Thu, 2 Oct 2025 12:10:59 +0200 Subject: [PATCH] Simple settings page #3 --- package-lock.json | 90 ++++++++++++++++ package.json | 2 + src/app/settings/page.tsx | 54 ++++++++++ src/app/settings/settings.tsx | 91 ++++++++++++++++ src/components/ui/checkbox.tsx | 32 ++++++ src/components/ui/select.tsx | 185 +++++++++++++++++++++++++++++++++ src/hooks/use-settings.ts | 18 ++++ src/lib/clientSettings.ts | 4 +- src/types/config.ts | 5 - src/types/settings.ts | 15 +++ 10 files changed, 489 insertions(+), 7 deletions(-) create mode 100644 src/app/settings/page.tsx create mode 100644 src/app/settings/settings.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/hooks/use-settings.ts delete mode 100644 src/types/config.ts create mode 100644 src/types/settings.ts diff --git a/package-lock.json b/package-lock.json index 74105af..ba29dfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,12 @@ "@lexical/react": "^0.34.0", "@lexical/rich-text": "^0.34.0", "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.13", @@ -1070,6 +1072,36 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", @@ -1519,6 +1551,49 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-separator": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.7.tgz", @@ -1727,6 +1802,21 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", diff --git a/package.json b/package.json index ceb1207..bfe1b65 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,12 @@ "@lexical/react": "^0.34.0", "@lexical/rich-text": "^0.34.0", "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-dropdown-menu": "^2.1.16", "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tabs": "^1.1.13", diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx new file mode 100644 index 0000000..527ac4e --- /dev/null +++ b/src/app/settings/page.tsx @@ -0,0 +1,54 @@ +"use client"; + +import { useClientSettings } from "@/hooks/use-settings"; +import { setClientSettings } from "@/lib/clientSettings"; +import Settings from "./settings"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Card } from "@/components/ui/card"; +import { CardContent } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { ACCOUNT_SCHEMA, CLIENT_SCHEMA } from "@/types/settings"; + +export default function SettingsPage() { + const [settings, setSettings] = useClientSettings(); + + return ( + + + Client Settings + Account Settings + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/app/settings/settings.tsx b/src/app/settings/settings.tsx new file mode 100644 index 0000000..2f3d938 --- /dev/null +++ b/src/app/settings/settings.tsx @@ -0,0 +1,91 @@ +"use client"; + +import { Dispatch, SetStateAction } from "react"; +import { Input } from "@/components/ui/input"; +import { Checkbox } from "@/components/ui/checkbox"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { StringMap } from "@/types/typeUtils"; + +export type SettingsSchema = StringMap< + "boolean" | "string" | "number" | string[] +>; + +export default function Settings({ + settings, + setSettings, + schema, +}: { + settings: T; + setSettings: Dispatch>; + schema: SettingsSchema; +}) { + const handleChange = (key: string, value: any) => { + setSettings((prev) => ({ ...prev, [key]: value })); + }; + + const renderInput = (key: string, type: any) => { + if (Array.isArray(type)) { + // Dropdown + return ( + + ); + } else if (type === "boolean") { + // Checkbox + return ( + handleChange(key, val)} + /> + ); + } else if (type === "string") { + // Text input + return ( + handleChange(key, e.target.value)} + /> + ); + } else if (type === "number") { + // Number input + return ( + handleChange(key, Number(e.target.value))} + /> + ); + } + }; + + return ( + <> + {Object.entries(schema).map(([key, type]) => ( +
+ + {renderInput(key, type)} +
+ ))} + + ); +} diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..fa0e4b5 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,32 @@ +"use client" + +import * as React from "react" +import * as CheckboxPrimitive from "@radix-ui/react-checkbox" +import { CheckIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Checkbox({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + + + ) +} + +export { Checkbox } diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..dcbbc0c --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,185 @@ +"use client" + +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react" + +import { cn } from "@/lib/utils" + +function Select({ + ...props +}: React.ComponentProps) { + return +} + +function SelectGroup({ + ...props +}: React.ComponentProps) { + return +} + +function SelectValue({ + ...props +}: React.ComponentProps) { + return +} + +function SelectTrigger({ + className, + size = "default", + children, + ...props +}: React.ComponentProps & { + size?: "sm" | "default" +}) { + return ( + + {children} + + + + + ) +} + +function SelectContent({ + className, + children, + position = "popper", + ...props +}: React.ComponentProps) { + return ( + + + + + {children} + + + + + ) +} + +function SelectLabel({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectItem({ + className, + children, + ...props +}: React.ComponentProps) { + return ( + + + + + + + {children} + + ) +} + +function SelectSeparator({ + className, + ...props +}: React.ComponentProps) { + return ( + + ) +} + +function SelectScrollUpButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +function SelectScrollDownButton({ + className, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectScrollDownButton, + SelectScrollUpButton, + SelectSeparator, + SelectTrigger, + SelectValue, +} diff --git a/src/hooks/use-settings.ts b/src/hooks/use-settings.ts new file mode 100644 index 0000000..a4e51f5 --- /dev/null +++ b/src/hooks/use-settings.ts @@ -0,0 +1,18 @@ +"use client"; + +import { defaultSettings, getClientSettings } from "@/lib/clientSettings"; +import { ClientSettings } from "@/types/settings"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; + +export function useClientSettings(): [ + ClientSettings, + Dispatch> +] { + const [settings, setSettings] = useState(defaultSettings); + + useEffect(() => { + setSettings(getClientSettings()); + }, []); + + return [settings, setSettings]; +} diff --git a/src/lib/clientSettings.ts b/src/lib/clientSettings.ts index 1822546..4a7e385 100644 --- a/src/lib/clientSettings.ts +++ b/src/lib/clientSettings.ts @@ -1,7 +1,7 @@ -import { ClientSettings } from "@/types/config"; +import { ClientSettings } from "@/types/settings"; import Cookies from "js-cookie"; -const defaultSettings: ClientSettings = { +export const defaultSettings: ClientSettings = { theme: "dark", }; diff --git a/src/types/config.ts b/src/types/config.ts deleted file mode 100644 index 7c196d0..0000000 --- a/src/types/config.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ClientSettings { - theme: "light" | "dark"; -} - -export interface AccountSettings {} diff --git a/src/types/settings.ts b/src/types/settings.ts new file mode 100644 index 0000000..22a5b15 --- /dev/null +++ b/src/types/settings.ts @@ -0,0 +1,15 @@ +import { SettingsSchema } from "@/app/settings/settings"; + +export interface ClientSettings { + theme: "light" | "dark"; +} + +export interface AccountSettings {} + +export const CLIENT_SCHEMA: SettingsSchema = { + theme: ["light", "dark"], +}; + +export const ACCOUNT_SCHEMA: SettingsSchema = { + node_ip: "string", +};