Regex settings #3

This commit is contained in:
2025-10-02 12:39:43 +02:00
parent 99a1b3b264
commit 0734dde01e
4 changed files with 97 additions and 25 deletions
+6 -13
View File
@@ -1,16 +1,16 @@
"use client";
import { useClientSettings } from "@/hooks/use-settings";
import { useAccountSettings, 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();
const [accountSettings, setAccountSettings] = useAccountSettings();
return (
<Tabs className="mx-auto" defaultValue="client">
@@ -25,13 +25,8 @@ export default function SettingsPage() {
settings={settings}
setSettings={setSettings}
schema={CLIENT_SCHEMA}
onSave={() => setClientSettings(settings)}
/>
<Button
onClick={() => setClientSettings(settings)}
variant="outline"
>
Save
</Button>
</CardContent>
</Card>
</TabsContent>
@@ -39,13 +34,11 @@ export default function SettingsPage() {
<Card className="w-xl mx-auto">
<CardContent className="flex flex-col gap-3">
<Settings
settings={settings}
setSettings={setSettings}
settings={accountSettings}
setSettings={setAccountSettings}
schema={ACCOUNT_SCHEMA}
onSave={() => {}}
/>
<Button onClick={() => {}} variant="outline">
Save
</Button>
</CardContent>
</Card>
</TabsContent>
+71 -8
View File
@@ -1,6 +1,4 @@
"use client";
import { Dispatch, SetStateAction } from "react";
import { Dispatch, SetStateAction, useState, useRef } from "react";
import { Input } from "@/components/ui/input";
import { Checkbox } from "@/components/ui/checkbox";
import {
@@ -11,27 +9,65 @@ import {
SelectValue,
} from "@/components/ui/select";
import { StringMap } from "@/types/typeUtils";
import { Button } from "@/components/ui/button";
export class RegexSetting {
placeholder: string;
pattern: RegExp;
constructor(placeholder: string, pattern: RegExp) {
this.placeholder = placeholder;
this.pattern = pattern;
}
}
export type SettingsSchema = StringMap<
"boolean" | "string" | "number" | string[]
"boolean" | "string" | "number" | string[] | RegexSetting
>;
export default function Settings<T>({
settings,
setSettings,
schema,
onSave,
}: {
settings: T;
setSettings: Dispatch<SetStateAction<T>>;
schema: SettingsSchema;
onSave: () => void;
}) {
const [feedback, setFeedback] = useState<
undefined | { kind: "error" | "info"; message: string }
>();
// Ref to store debounce timer
const debounceRef = useRef<NodeJS.Timeout>(undefined);
const handleChange = (key: string, value: any) => {
setSettings((prev) => ({ ...prev, [key]: value }));
};
const handleRegexChange = (
key: string,
regexSetting: RegexSetting,
value: string
) => {
handleChange(key, value);
if (debounceRef.current) clearTimeout(debounceRef.current);
debounceRef.current = setTimeout(() => {
if (!regexSetting.pattern.test(value)) {
setFeedback({
kind: "error",
message: `Value does not match the pattern!`,
});
} else {
setFeedback(undefined);
}
}, 1500);
};
const renderInput = (key: string, type: any) => {
if (Array.isArray(type)) {
// Dropdown
return (
<Select
value={(settings as any)[key]}
@@ -50,7 +86,6 @@ export default function Settings<T>({
</Select>
);
} else if (type === "boolean") {
// Checkbox
return (
<Checkbox
checked={(settings as any)[key]}
@@ -58,7 +93,6 @@ export default function Settings<T>({
/>
);
} else if (type === "string") {
// Text input
return (
<Input
type="text"
@@ -67,7 +101,6 @@ export default function Settings<T>({
/>
);
} else if (type === "number") {
// Number input
return (
<Input
type="number"
@@ -75,6 +108,15 @@ export default function Settings<T>({
onChange={(e) => handleChange(key, Number(e.target.value))}
/>
);
} else if (type instanceof RegexSetting) {
return (
<Input
type="text"
value={(settings as any)[key] || ""}
placeholder={type.placeholder}
onChange={(e) => handleRegexChange(key, type, e.target.value)}
/>
);
}
};
@@ -86,6 +128,27 @@ export default function Settings<T>({
{renderInput(key, type)}
</div>
))}
{feedback && (
<div
className={`text-sm rounded-md p-2 ${
feedback.kind === "error"
? "bg-red-500/20 text-red-400"
: "bg-green-500/20 text-green-400"
}`}
>
{feedback.message}
</div>
)}
<Button
onClick={() => {
if (feedback === undefined) onSave();
}}
variant="outline"
>
Save
</Button>
</>
);
}
+12 -1
View File
@@ -1,7 +1,7 @@
"use client";
import { defaultSettings, getClientSettings } from "@/lib/clientSettings";
import { ClientSettings } from "@/types/settings";
import { AccountSettings, ClientSettings } from "@/types/settings";
import { Dispatch, SetStateAction, useEffect, useState } from "react";
export function useClientSettings(): [
@@ -16,3 +16,14 @@ export function useClientSettings(): [
return [settings, setSettings];
}
export function useAccountSettings(): [
AccountSettings,
Dispatch<SetStateAction<AccountSettings>>
] {
const [settings, setSettings] = useState<AccountSettings>({
node_ip: "node0.voxa.org",
});
return [settings, setSettings];
}
+8 -3
View File
@@ -1,15 +1,20 @@
import { SettingsSchema } from "@/app/settings/settings";
import { RegexSetting, SettingsSchema } from "@/app/settings/settings";
export interface ClientSettings {
theme: "light" | "dark";
}
export interface AccountSettings {}
export interface AccountSettings {
node_ip: string;
}
export const CLIENT_SCHEMA: SettingsSchema = {
theme: ["light", "dark"],
};
export const ACCOUNT_SCHEMA: SettingsSchema = {
node_ip: "string",
node_ip: new RegexSetting(
"node2.voxa.org",
/^(?:(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}|(?:\d{1,3}\.){3}\d{1,3})$/
),
};