Regex settings #3
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useClientSettings } from "@/hooks/use-settings";
|
import { useAccountSettings, useClientSettings } from "@/hooks/use-settings";
|
||||||
import { setClientSettings } from "@/lib/clientSettings";
|
import { setClientSettings } from "@/lib/clientSettings";
|
||||||
import Settings from "./settings";
|
import Settings from "./settings";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
import { CardContent } from "@/components/ui/card";
|
import { CardContent } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ACCOUNT_SCHEMA, CLIENT_SCHEMA } from "@/types/settings";
|
import { ACCOUNT_SCHEMA, CLIENT_SCHEMA } from "@/types/settings";
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
const [settings, setSettings] = useClientSettings();
|
const [settings, setSettings] = useClientSettings();
|
||||||
|
const [accountSettings, setAccountSettings] = useAccountSettings();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tabs className="mx-auto" defaultValue="client">
|
<Tabs className="mx-auto" defaultValue="client">
|
||||||
@@ -25,13 +25,8 @@ export default function SettingsPage() {
|
|||||||
settings={settings}
|
settings={settings}
|
||||||
setSettings={setSettings}
|
setSettings={setSettings}
|
||||||
schema={CLIENT_SCHEMA}
|
schema={CLIENT_SCHEMA}
|
||||||
|
onSave={() => setClientSettings(settings)}
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
onClick={() => setClientSettings(settings)}
|
|
||||||
variant="outline"
|
|
||||||
>
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
@@ -39,13 +34,11 @@ export default function SettingsPage() {
|
|||||||
<Card className="w-xl mx-auto">
|
<Card className="w-xl mx-auto">
|
||||||
<CardContent className="flex flex-col gap-3">
|
<CardContent className="flex flex-col gap-3">
|
||||||
<Settings
|
<Settings
|
||||||
settings={settings}
|
settings={accountSettings}
|
||||||
setSettings={setSettings}
|
setSettings={setAccountSettings}
|
||||||
schema={ACCOUNT_SCHEMA}
|
schema={ACCOUNT_SCHEMA}
|
||||||
|
onSave={() => {}}
|
||||||
/>
|
/>
|
||||||
<Button onClick={() => {}} variant="outline">
|
|
||||||
Save
|
|
||||||
</Button>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
"use client";
|
import { Dispatch, SetStateAction, useState, useRef } from "react";
|
||||||
|
|
||||||
import { Dispatch, SetStateAction } from "react";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import {
|
import {
|
||||||
@@ -11,27 +9,65 @@ import {
|
|||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select";
|
} from "@/components/ui/select";
|
||||||
import { StringMap } from "@/types/typeUtils";
|
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<
|
export type SettingsSchema = StringMap<
|
||||||
"boolean" | "string" | "number" | string[]
|
"boolean" | "string" | "number" | string[] | RegexSetting
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export default function Settings<T>({
|
export default function Settings<T>({
|
||||||
settings,
|
settings,
|
||||||
setSettings,
|
setSettings,
|
||||||
schema,
|
schema,
|
||||||
|
onSave,
|
||||||
}: {
|
}: {
|
||||||
settings: T;
|
settings: T;
|
||||||
setSettings: Dispatch<SetStateAction<T>>;
|
setSettings: Dispatch<SetStateAction<T>>;
|
||||||
schema: SettingsSchema;
|
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) => {
|
const handleChange = (key: string, value: any) => {
|
||||||
setSettings((prev) => ({ ...prev, [key]: value }));
|
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) => {
|
const renderInput = (key: string, type: any) => {
|
||||||
if (Array.isArray(type)) {
|
if (Array.isArray(type)) {
|
||||||
// Dropdown
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
value={(settings as any)[key]}
|
value={(settings as any)[key]}
|
||||||
@@ -50,7 +86,6 @@ export default function Settings<T>({
|
|||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
} else if (type === "boolean") {
|
} else if (type === "boolean") {
|
||||||
// Checkbox
|
|
||||||
return (
|
return (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={(settings as any)[key]}
|
checked={(settings as any)[key]}
|
||||||
@@ -58,7 +93,6 @@ export default function Settings<T>({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (type === "string") {
|
} else if (type === "string") {
|
||||||
// Text input
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
@@ -67,7 +101,6 @@ export default function Settings<T>({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (type === "number") {
|
} else if (type === "number") {
|
||||||
// Number input
|
|
||||||
return (
|
return (
|
||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
@@ -75,6 +108,15 @@ export default function Settings<T>({
|
|||||||
onChange={(e) => handleChange(key, Number(e.target.value))}
|
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)}
|
{renderInput(key, type)}
|
||||||
</div>
|
</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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { defaultSettings, getClientSettings } from "@/lib/clientSettings";
|
import { defaultSettings, getClientSettings } from "@/lib/clientSettings";
|
||||||
import { ClientSettings } from "@/types/settings";
|
import { AccountSettings, ClientSettings } from "@/types/settings";
|
||||||
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
import { Dispatch, SetStateAction, useEffect, useState } from "react";
|
||||||
|
|
||||||
export function useClientSettings(): [
|
export function useClientSettings(): [
|
||||||
@@ -16,3 +16,14 @@ export function useClientSettings(): [
|
|||||||
|
|
||||||
return [settings, setSettings];
|
return [settings, setSettings];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useAccountSettings(): [
|
||||||
|
AccountSettings,
|
||||||
|
Dispatch<SetStateAction<AccountSettings>>
|
||||||
|
] {
|
||||||
|
const [settings, setSettings] = useState<AccountSettings>({
|
||||||
|
node_ip: "node0.voxa.org",
|
||||||
|
});
|
||||||
|
|
||||||
|
return [settings, setSettings];
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,15 +1,20 @@
|
|||||||
import { SettingsSchema } from "@/app/settings/settings";
|
import { RegexSetting, SettingsSchema } from "@/app/settings/settings";
|
||||||
|
|
||||||
export interface ClientSettings {
|
export interface ClientSettings {
|
||||||
theme: "light" | "dark";
|
theme: "light" | "dark";
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AccountSettings {}
|
export interface AccountSettings {
|
||||||
|
node_ip: string;
|
||||||
|
}
|
||||||
|
|
||||||
export const CLIENT_SCHEMA: SettingsSchema = {
|
export const CLIENT_SCHEMA: SettingsSchema = {
|
||||||
theme: ["light", "dark"],
|
theme: ["light", "dark"],
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ACCOUNT_SCHEMA: SettingsSchema = {
|
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})$/
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user