Browse Source

validation

pull/266/head
Hunter Thornsberry 2 years ago
parent
commit
1eedb6d97b
  1. 5
      src/components/Form/DynamicForm.tsx
  2. 12
      src/components/Form/FormPasswordGenerator.tsx
  3. 5
      src/components/Form/FormWrapper.tsx
  4. 54
      src/components/PageComponents/Channel.tsx
  5. 67
      src/components/UI/Generator.tsx
  6. 31
      src/components/UI/Input.tsx

5
src/components/Form/DynamicForm.tsx

@ -26,6 +26,7 @@ export interface BaseFormBuilderProps<T> {
disabledBy?: DisabledBy<T>[]; disabledBy?: DisabledBy<T>[];
label: string; label: string;
description?: string; description?: string;
validationText?: string;
properties?: Record<string, unknown>; properties?: Record<string, unknown>;
} }
@ -44,6 +45,8 @@ export interface DynamicFormProps<T extends FieldValues> {
fieldGroups: { fieldGroups: {
label: string; label: string;
description: string; description: string;
valid?: boolean;
validationText?: string;
fields: FieldProps<T>[]; fields: FieldProps<T>[];
}[]; }[];
} }
@ -98,6 +101,8 @@ export function DynamicForm<T extends FieldValues>({
key={field.label} key={field.label}
label={field.label} label={field.label}
description={field.description} description={field.description}
valid={field.validationText == undefined || field.validationText == ""}
validationText={field.validationText}
> >
<DynamicFormField <DynamicFormField
field={field} field={field}

12
src/components/Form/FormPasswordGenerator.tsx

@ -3,11 +3,15 @@ import type {
GenericFormElementProps, GenericFormElementProps,
} from "@components/Form/DynamicForm.js"; } from "@components/Form/DynamicForm.js";
import { Generator } from "@components/UI/Generator.js"; import { Generator } from "@components/UI/Generator.js";
import { ChangeEventHandler, MouseEventHandler } from "react";
import { Controller, type FieldValues } from "react-hook-form"; import { Controller, type FieldValues } from "react-hook-form";
export interface PasswordGeneratorProps<T> extends BaseFormBuilderProps<T> { export interface PasswordGeneratorProps<T> extends BaseFormBuilderProps<T> {
type: "passwordGenerator"; type: "passwordGenerator";
devicePSKBitCount: number; devicePSKBitCount: number;
inputChange: ChangeEventHandler;
selectChange: (event: string) => void;
buttonClick: MouseEventHandler;
} }
export function PasswordGenerator<T extends FieldValues>({ export function PasswordGenerator<T extends FieldValues>({
@ -18,12 +22,14 @@ export function PasswordGenerator<T extends FieldValues>({
<Controller <Controller
name={field.name} name={field.name}
control={control} control={control}
render={({ field: { value, onChange, ...rest } }) => ( render={({ field: { value,...rest } }) => (
<Generator <Generator
devicePSKBitCount={field.devicePSKBitCount} devicePSKBitCount={field.devicePSKBitCount}
changeEvent={onChange} inputChange={field.inputChange}
selectChange={field.selectChange}
buttonClick={field.buttonClick}
value={value} value={value}
variant={"success"} variant={field.validationText ? "invalid" : "default"}
buttonText="Generate" buttonText="Generate"
{...field.properties} {...field.properties}
{...rest} {...rest}

5
src/components/Form/FormWrapper.tsx

@ -5,12 +5,16 @@ export interface FieldWrapperProps {
description?: string; description?: string;
disabled?: boolean; disabled?: boolean;
children?: React.ReactNode; children?: React.ReactNode;
valid?: boolean;
validationText?: string;
} }
export const FieldWrapper = ({ export const FieldWrapper = ({
label, label,
description, description,
children, children,
valid,
validationText,
}: FieldWrapperProps): JSX.Element => ( }: FieldWrapperProps): JSX.Element => (
<div className="pt-6 sm:pt-5"> <div className="pt-6 sm:pt-5">
<div role="group" aria-labelledby="label-notifications"> <div role="group" aria-labelledby="label-notifications">
@ -19,6 +23,7 @@ export const FieldWrapper = ({
<div className="sm:col-span-2"> <div className="sm:col-span-2">
<div className="max-w-lg"> <div className="max-w-lg">
<p className="text-sm text-gray-500">{description}</p> <p className="text-sm text-gray-500">{description}</p>
<p hidden={valid ?? true} className="text-sm text-red-500">{validationText}</p>
<div className="mt-4 space-y-4"> <div className="mt-4 space-y-4">
<div className="flex items-center">{children}</div> <div className="flex items-center">{children}</div>
</div> </div>

54
src/components/PageComponents/Channel.tsx

@ -21,6 +21,7 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
const [bitCount, setBits] = useState<number>( const [bitCount, setBits] = useState<number>(
channel?.settings?.psk.length ?? 16, channel?.settings?.psk.length ?? 16,
); );
const [validationText, setValidationText] = useState<string>();
const onSubmit = (data: ChannelValidation) => { const onSubmit = (data: ChannelValidation) => {
const channel = new Protobuf.Channel.Channel({ const channel = new Protobuf.Channel.Channel({
@ -54,8 +55,53 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
}), }),
), ),
); );
setValidationText(undefined);
}; };
const validatePass = (input: string, count: number) => {
if (count == 32) {
if (input.length != 44) {
setValidationText("Please enter a valid 256 bit PSK.");
}
else {
setValidationText(undefined);
}
}
else if (count == 16)
{
if (input.length != 24) {
setValidationText("Please enter a valid 128 bit PSK.");
}
else {
setValidationText(undefined);
}
}
else if (count == 1)
{
if (input.length != 4) {
setValidationText("Please enter a valid 1 bit PSK");
}
else {
setValidationText(undefined);
}
}
else {
setValidationText("Unkown PSK length.");
}
}
const inputChangeEvent = (e) => {
let psk = e.currentTarget?.value;
setPass(psk);
validatePass(psk, bitCount);
};
const selectChangeEvent = (e: string) => {
let count = Number.parseInt(e);
setBits(count);
validatePass(pass, count);
}
return ( return (
<DynamicForm<ChannelValidation> <DynamicForm<ChannelValidation>
onSubmit={onSubmit} onSubmit={onSubmit}
@ -100,11 +146,13 @@ export const Channel = ({ channel }: SettingsPanelProps): JSX.Element => {
name: "settings.psk", name: "settings.psk",
label: "pre-Shared Key", label: "pre-Shared Key",
description: "256, 128, or 8 bit PSKs allowed", description: "256, 128, or 8 bit PSKs allowed",
validationText: validationText,
devicePSKBitCount: bitCount ?? 0, devicePSKBitCount: bitCount ?? 0,
inputChange: inputChangeEvent,
selectChange: selectChangeEvent,
buttonClick: clickEvent,
properties: { properties: {
value: pass, value: pass
onClick: clickEvent,
changeEvent: (e: string) => setBits(Number.parseInt(e)),
}, },
}, },
{ {

67
src/components/UI/Generator.tsx

@ -1,4 +1,3 @@
import { type VariantProps, cva } from "class-variance-authority";
import * as React from "react"; import * as React from "react";
import { Button } from "@components/UI/Button.js"; import { Button } from "@components/UI/Button.js";
@ -11,67 +10,35 @@ import {
SelectValue, SelectValue,
} from "@components/UI/Select.js"; } from "@components/UI/Select.js";
const generatorVariants = cva(
"inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2",
{
variants: {
variant: {
default: "",
destructive:
"bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600",
success:
"bg-green-500 text-white hover:bg-green-600 dark:hover:bg-green-600",
outline:
"bg-transparent border border-slate-200 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-100",
subtle:
"bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-700 dark:text-slate-100",
ghost:
"bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent",
link: "bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent",
},
size: {
default: "h-10 py-2 px-4",
sm: "h-9 px-2 rounded-md",
lg: "h-11 px-8 rounded-md",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
},
);
export interface GeneratorProps export interface GeneratorProps
extends React.BaseHTMLAttributes<HTMLElement>, extends React.BaseHTMLAttributes<HTMLElement> {
VariantProps<typeof generatorVariants> {
devicePSKBitCount?: number; devicePSKBitCount?: number;
value: string; value: string;
variant: "default" | "invalid";
buttonText?: string; buttonText?: string;
changeEvent: (event: string) => void; selectChange: (event: string) => void;
inputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
buttonClick: React.MouseEventHandler<HTMLButtonElement>;
} }
const getBitString = (bitcount?: number) => {
if (bitcount === 32) {
return "32";
}
if (bitcount === 1) {
return "1";
}
return "16";
};
const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>( const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
( (
{ devicePSKBitCount, value, buttonText, variant, changeEvent, ...props }, { devicePSKBitCount, variant, value, buttonText, selectChange, inputChange, buttonClick, ...props },
ref, ref,
) => { ) => {
return ( return (
<> <>
<Input type="text" id="pskInput" value={value} /> <Input
type="text"
id="pskInput"
variant={variant}
value={value}
onChange={inputChange}
/>
<Select <Select
value={getBitString(devicePSKBitCount)} value={devicePSKBitCount?.toString()}
onValueChange={(e) => changeEvent(e)} onValueChange={(e) => selectChange(e)}
> >
<SelectTrigger> <SelectTrigger>
<SelectValue /> <SelectValue />
@ -88,7 +55,7 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
</SelectItem> </SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<Button type="button" variant="success" {...props}> <Button type="button" variant="success" onClick={buttonClick} {...props}>
{buttonText} {buttonText}
</Button> </Button>
</> </>
@ -97,4 +64,4 @@ const Generator = React.forwardRef<HTMLInputElement, GeneratorProps>(
); );
Generator.displayName = "Button"; Generator.displayName = "Button";
export { Generator, generatorVariants }; export { Generator };

31
src/components/UI/Input.tsx

@ -2,9 +2,28 @@ import * as React from "react";
import { cn } from "@core/utils/cn.js"; import { cn } from "@core/utils/cn.js";
import type { LucideIcon } from "lucide-react"; import type { LucideIcon } from "lucide-react";
import { cva, VariantProps } from "class-variance-authority";
const inputVariants = cva(
"flex h-10 w-full rounded-md border bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
{
variants: {
variant: {
default:
"border-slate-300 dark:border-slate-700",
invalid:
"border-red-500 dark:border-red-500",
},
},
defaultVariants: {
variant: "default",
},
},
);
export interface InputProps export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> { extends React.InputHTMLAttributes<HTMLInputElement>,
VariantProps<typeof inputVariants> {
prefix?: string; prefix?: string;
suffix?: string; suffix?: string;
action?: { action?: {
@ -14,7 +33,7 @@ export interface InputProps
} }
const Input = React.forwardRef<HTMLInputElement, InputProps>( const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, prefix, suffix, action, ...props }, ref) => { ({ className, variant, prefix, suffix, action, ...props }, ref) => {
return ( return (
<div className="relative w-full"> <div className="relative w-full">
{prefix && ( {prefix && (
@ -23,11 +42,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
</span> </span>
)} )}
<input <input
className={cn( className={cn(action && "pr-8", className, inputVariants({ variant }))}
"flex h-10 w-full rounded-md border border-slate-300 bg-transparent py-2 px-3 text-sm placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-700 dark:text-slate-50 dark:focus:ring-slate-400 dark:focus:ring-offset-slate-900",
action && "pr-8",
className,
)}
ref={ref} ref={ref}
{...props} {...props}
/> />
@ -51,4 +66,4 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
); );
Input.displayName = "Input"; Input.displayName = "Input";
export { Input }; export { Input, inputVariants };

Loading…
Cancel
Save