Regex settings #3
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user