refactor: switch to using Bun (#718)
This commit is contained in:
@@ -1,10 +1,15 @@
|
||||
import { createZodResolver } from "@components/Form/createZodResolver.ts";
|
||||
import {
|
||||
DynamicFormField,
|
||||
type FieldProps,
|
||||
} from "@components/Form/DynamicFormField.tsx";
|
||||
import { FieldWrapper } from "@components/Form/FormWrapper.tsx";
|
||||
import { Button } from "@components/UI/Button.tsx";
|
||||
import { Heading } from "@components/UI/Typography/Heading.tsx";
|
||||
import { Subtle } from "@components/UI/Typography/Subtle.tsx";
|
||||
import { useAppStore } from "@core/stores/appStore.ts";
|
||||
import { dotPaths } from "@core/utils/dotPath.ts";
|
||||
import { useEffect } from "react";
|
||||
import {
|
||||
type Control,
|
||||
type DefaultValues,
|
||||
@@ -13,16 +18,11 @@ import {
|
||||
get,
|
||||
type Path,
|
||||
type SubmitHandler,
|
||||
useForm,
|
||||
type UseFormReturn,
|
||||
useForm,
|
||||
} from "react-hook-form";
|
||||
import { Heading } from "@components/UI/Typography/Heading.tsx";
|
||||
import { ZodType } from "zod/v4";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createZodResolver } from "@components/Form/createZodResolver.ts";
|
||||
import { useAppStore } from "@core/stores/appStore.ts";
|
||||
import { dotPaths } from "@core/utils/dotPath.ts";
|
||||
import type { ZodType } from "zod/v4";
|
||||
|
||||
interface DisabledBy<T> {
|
||||
fieldName: Path<T>;
|
||||
@@ -86,41 +86,34 @@ export function DynamicForm<T extends FieldValues>({
|
||||
formId,
|
||||
}: DynamicFormProps<T>) {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
addError,
|
||||
removeError,
|
||||
} = useAppStore();
|
||||
const { addError, removeError } = useAppStore();
|
||||
|
||||
const internalMethods = useForm<T>({
|
||||
mode: "onChange",
|
||||
defaultValues: defaultValues,
|
||||
resolver: validationSchema
|
||||
? createZodResolver(validationSchema)
|
||||
: undefined,
|
||||
shouldFocusError: false,
|
||||
resetOptions: { keepDefaultValues: true },
|
||||
values,
|
||||
});
|
||||
|
||||
const methods = propMethods ?? internalMethods;
|
||||
|
||||
let methods = propMethods;
|
||||
if (!methods) {
|
||||
methods = useForm<
|
||||
T
|
||||
>({
|
||||
mode: "onChange",
|
||||
defaultValues: defaultValues,
|
||||
resolver: validationSchema
|
||||
? createZodResolver(validationSchema)
|
||||
: undefined,
|
||||
shouldFocusError: false,
|
||||
resetOptions: { keepDefaultValues: true },
|
||||
values,
|
||||
});
|
||||
}
|
||||
const { handleSubmit, control, getValues, formState, getFieldState } =
|
||||
methods;
|
||||
|
||||
useEffect(() => {
|
||||
if (!propMethods) {
|
||||
onFormInit?.(methods);
|
||||
onFormInit?.(internalMethods);
|
||||
}
|
||||
}, [onFormInit, propMethods, methods]);
|
||||
}, [onFormInit, propMethods, internalMethods]);
|
||||
|
||||
useEffect(() => {
|
||||
const errorKeys = Object.keys(formState.errors);
|
||||
if (formId) {
|
||||
if (
|
||||
errorKeys.length === 0
|
||||
) {
|
||||
if (errorKeys.length === 0) {
|
||||
dotPaths(getValues()).forEach((key) => {
|
||||
removeError(key);
|
||||
});
|
||||
@@ -132,19 +125,27 @@ export function DynamicForm<T extends FieldValues>({
|
||||
addError(formId, "");
|
||||
}
|
||||
}
|
||||
}, [formState.errors]);
|
||||
}, [formState.errors, addError, formId, getValues, removeError]);
|
||||
|
||||
const isDisabled = (
|
||||
disabledBy?: DisabledBy<T>[],
|
||||
disabled?: boolean,
|
||||
): boolean => {
|
||||
if (disabled) return true;
|
||||
if (!disabledBy) return false;
|
||||
if (disabled) {
|
||||
return true;
|
||||
}
|
||||
if (!disabledBy) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return disabledBy.some((field) => {
|
||||
const value = getValues(field.fieldName);
|
||||
if (value === "always") return true;
|
||||
if (typeof value === "boolean") return field.invert ? value : !value;
|
||||
if (value === "always") {
|
||||
return true;
|
||||
}
|
||||
if (typeof value === "boolean") {
|
||||
return field.invert ? value : !value;
|
||||
}
|
||||
if (typeof value === "number") {
|
||||
return field.invert
|
||||
? field.selector !== value
|
||||
@@ -181,14 +182,16 @@ export function DynamicForm<T extends FieldValues>({
|
||||
fieldName={field.name}
|
||||
description={field.description}
|
||||
valid={!error}
|
||||
validationText={error
|
||||
? String(
|
||||
t([`formValidation.${error.type}`, error.message], {
|
||||
returnObjects: false,
|
||||
...error.params,
|
||||
}),
|
||||
)
|
||||
: ""}
|
||||
validationText={
|
||||
error
|
||||
? String(
|
||||
t([`formValidation.${error.type}`, error.message], {
|
||||
returnObjects: false,
|
||||
...error.params,
|
||||
}),
|
||||
)
|
||||
: ""
|
||||
}
|
||||
>
|
||||
<DynamicFormField
|
||||
field={field}
|
||||
@@ -203,11 +206,7 @@ export function DynamicForm<T extends FieldValues>({
|
||||
</div>
|
||||
))}
|
||||
{hasSubmitButton && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
disabled={!formState.isValid}
|
||||
>
|
||||
<Button type="submit" variant="outline" disabled={!formState.isValid}>
|
||||
{t("button.submit")}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
type MultiSelectFieldProps,
|
||||
MultiSelectInput,
|
||||
} from "./FormMultiSelect.tsx";
|
||||
import {
|
||||
GenericInput,
|
||||
type InputFieldProps,
|
||||
@@ -19,6 +15,10 @@ import {
|
||||
ToggleInput,
|
||||
} from "@components/Form/FormToggle.tsx";
|
||||
import type { Control, FieldValues } from "react-hook-form";
|
||||
import {
|
||||
type MultiSelectFieldProps,
|
||||
MultiSelectInput,
|
||||
} from "./FormMultiSelect.tsx";
|
||||
|
||||
export type FieldProps<T> =
|
||||
| InputFieldProps<T>
|
||||
|
||||
@@ -58,7 +58,9 @@ export function GenericInput<T extends FieldValues>({
|
||||
return;
|
||||
}
|
||||
|
||||
if (field.inputChange) field.inputChange(e);
|
||||
if (field.inputChange) {
|
||||
field.inputChange(e);
|
||||
}
|
||||
|
||||
controllerField.onChange(
|
||||
field.type === "number"
|
||||
@@ -76,14 +78,17 @@ export function GenericInput<T extends FieldValues>({
|
||||
<Input
|
||||
type={field.type}
|
||||
step={field.properties?.step}
|
||||
value={field.type === "number"
|
||||
? String(controllerField.value)
|
||||
: controllerField.value}
|
||||
value={
|
||||
field.type === "number"
|
||||
? String(controllerField.value)
|
||||
: controllerField.value
|
||||
}
|
||||
id={field.name}
|
||||
onChange={handleInputChange}
|
||||
showCopyButton={field.properties?.showCopyButton}
|
||||
showPasswordToggle={field.properties?.showPasswordToggle ||
|
||||
field.type === "password"}
|
||||
showPasswordToggle={
|
||||
field.properties?.showPasswordToggle || field.type === "password"
|
||||
}
|
||||
className={field.properties?.className}
|
||||
{...restProperties}
|
||||
disabled={disabled}
|
||||
|
||||
@@ -2,11 +2,11 @@ import type {
|
||||
BaseFormBuilderProps,
|
||||
GenericFormElementProps,
|
||||
} from "@components/Form/DynamicForm.tsx";
|
||||
import type { FLAGS_CONFIG } from "@core/hooks/usePositionFlags.ts";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import type { FieldValues } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { FLAGS_CONFIG } from "@core/hooks/usePositionFlags.ts";
|
||||
import { MultiSelect, MultiSelectItem } from "../UI/MultiSelect.tsx";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
|
||||
export interface MultiSelectFieldProps<T> extends BaseFormBuilderProps<T> {
|
||||
type: "multiSelect";
|
||||
@@ -15,9 +15,7 @@ export interface MultiSelectFieldProps<T> extends BaseFormBuilderProps<T> {
|
||||
isChecked: (name: string) => boolean;
|
||||
value: string[];
|
||||
properties: BaseFormBuilderProps<T>["properties"] & {
|
||||
enumValue:
|
||||
| { [s: string]: string | number }
|
||||
| typeof FLAGS_CONFIG;
|
||||
enumValue: { [s: string]: string | number } | typeof FLAGS_CONFIG;
|
||||
formatEnumName?: boolean;
|
||||
};
|
||||
}
|
||||
@@ -39,7 +37,7 @@ export function MultiSelectInput<T extends FieldValues>({
|
||||
([key, configOrValue]) => {
|
||||
if (isNewConfigStructure) {
|
||||
const config =
|
||||
configOrValue as typeof FLAGS_CONFIG[keyof typeof FLAGS_CONFIG];
|
||||
configOrValue as (typeof FLAGS_CONFIG)[keyof typeof FLAGS_CONFIG];
|
||||
return {
|
||||
key,
|
||||
display: t(config.i18nKey),
|
||||
|
||||
@@ -2,11 +2,11 @@ import type {
|
||||
BaseFormBuilderProps,
|
||||
GenericFormElementProps,
|
||||
} from "@components/Form/DynamicForm.tsx";
|
||||
import type { ButtonVariant } from "../UI/Button.tsx";
|
||||
import { Generator } from "@components/UI/Generator.tsx";
|
||||
import { Controller, type FieldValues, useFormContext } from "react-hook-form";
|
||||
import { usePasswordVisibilityToggle } from "@core/hooks/usePasswordVisibilityToggle.ts";
|
||||
import { useEffect } from "react";
|
||||
import { Controller, type FieldValues, useFormContext } from "react-hook-form";
|
||||
import type { ButtonVariant } from "../UI/Button.tsx";
|
||||
|
||||
export interface PasswordGeneratorProps<T> extends BaseFormBuilderProps<T> {
|
||||
type: "passwordGenerator";
|
||||
@@ -38,24 +38,22 @@ export function PasswordGenerator<T extends FieldValues>({
|
||||
|
||||
useEffect(() => {
|
||||
trigger(field.name);
|
||||
}, [field.devicePSKBitCount, field.name, trigger]);
|
||||
}, [field.name, trigger]);
|
||||
|
||||
return (
|
||||
<Controller
|
||||
name={field.name}
|
||||
control={control}
|
||||
render={(
|
||||
{
|
||||
field: { value, onChange, ...rest },
|
||||
},
|
||||
) => (
|
||||
render={({ field: { value, onChange, ...rest } }) => (
|
||||
<Generator
|
||||
type={field.hide && !isVisible ? "password" : "text"}
|
||||
id={field.id}
|
||||
devicePSKBitCount={field.devicePSKBitCount}
|
||||
bits={field.bits}
|
||||
inputChange={(e) => {
|
||||
if (field.inputChange) field.inputChange(e);
|
||||
if (field.inputChange) {
|
||||
field.inputChange(e);
|
||||
}
|
||||
onChange(e);
|
||||
}}
|
||||
selectChange={field.selectChange ?? (() => {})}
|
||||
|
||||
@@ -9,8 +9,8 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@components/UI/Select.tsx";
|
||||
import { type FieldValues, useController } from "react-hook-form";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { type FieldValues, useController } from "react-hook-form";
|
||||
|
||||
export interface SelectFieldProps<T> extends BaseFormBuilderProps<T> {
|
||||
type: "select";
|
||||
@@ -73,10 +73,14 @@ export function SelectInput<T extends FieldValues>({
|
||||
|
||||
if (field.validate) {
|
||||
const isValid = await field.validate(selectedKey);
|
||||
if (!isValid) return;
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (field.selectChange) field.selectChange(newValue, selectedKey);
|
||||
if (field.selectChange) {
|
||||
field.selectChange(newValue, selectedKey);
|
||||
}
|
||||
onChange(Number.parseInt(newValue));
|
||||
};
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import type {
|
||||
GenericFormElementProps,
|
||||
} from "@components/Form/DynamicForm.tsx";
|
||||
import { Switch } from "@components/UI/Switch.tsx";
|
||||
import { Controller, type FieldValues } from "react-hook-form";
|
||||
import { cn } from "@core/utils/cn.ts";
|
||||
import { Controller, type FieldValues } from "react-hook-form";
|
||||
|
||||
export interface ToggleFieldProps<T> extends BaseFormBuilderProps<T> {
|
||||
type: "toggle";
|
||||
@@ -22,9 +22,7 @@ export function ToggleInput<T extends FieldValues>({
|
||||
<Controller
|
||||
name={field.name}
|
||||
control={control}
|
||||
render={(
|
||||
{ field: { value, onChange, ...rest } },
|
||||
) => (
|
||||
render={({ field: { value, onChange, ...rest } }) => (
|
||||
<Switch
|
||||
checked={value}
|
||||
onCheckedChange={(v) => {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import type { ZodType } from "zod/v4";
|
||||
import type {
|
||||
FieldError,
|
||||
FieldValues,
|
||||
@@ -6,6 +5,7 @@ import type {
|
||||
ResolverOptions,
|
||||
ResolverResult,
|
||||
} from "react-hook-form";
|
||||
import type { ZodType } from "zod/v4";
|
||||
|
||||
export function createZodResolver<T extends FieldValues>(
|
||||
schema: ZodType<T, unknown>,
|
||||
@@ -32,18 +32,18 @@ export function createZodResolver<T extends FieldValues>(
|
||||
const { path, code, message, ...params } = issue;
|
||||
const key = path.join(".");
|
||||
|
||||
const suffix = "format" in params
|
||||
? params.format
|
||||
: "origin" in params
|
||||
? params.origin
|
||||
: "expected" in params
|
||||
? params.expected
|
||||
: "";
|
||||
const suffix =
|
||||
"format" in params
|
||||
? params.format
|
||||
: "origin" in params
|
||||
? params.origin
|
||||
: "expected" in params
|
||||
? params.expected
|
||||
: "";
|
||||
|
||||
const newCode = code.replace(
|
||||
/_([a-z])/g,
|
||||
(_, char) => char.toUpperCase(),
|
||||
) + (suffix ? `.${suffix}` : "");
|
||||
const newCode =
|
||||
code.replace(/_([a-z])/g, (_, char) => char.toUpperCase()) +
|
||||
(suffix ? `.${suffix}` : "");
|
||||
|
||||
const fieldError: FieldError & { params?: Record<string, unknown> } = {
|
||||
type: newCode,
|
||||
|
||||
Reference in New Issue
Block a user