refactor: refactor subscriptions
This commit is contained in:
@@ -7,47 +7,51 @@ interface ErrorDisplayProps {
|
||||
| string
|
||||
| StandardSchemaV1Issue
|
||||
| Array<string | StandardSchemaV1Issue | undefined>;
|
||||
isDirty: boolean;
|
||||
submissionAttempts: number;
|
||||
}
|
||||
|
||||
export function FormFieldErrors({ errors }: ErrorDisplayProps) {
|
||||
export function FormFieldErrors({
|
||||
errors,
|
||||
isDirty,
|
||||
submissionAttempts,
|
||||
}: ErrorDisplayProps) {
|
||||
const errorList = useMemo(
|
||||
() =>
|
||||
(Array.isArray(errors) ? errors : [errors]).filter(Boolean) as Array<
|
||||
string | StandardSchemaV1Issue
|
||||
>,
|
||||
Array.from(
|
||||
new Set(
|
||||
(Array.isArray(errors) ? errors : [errors])
|
||||
.map((e) => {
|
||||
if (typeof e === "string") {
|
||||
return e;
|
||||
}
|
||||
if (e?.message) {
|
||||
return e.message;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Boolean) as string[]
|
||||
)
|
||||
),
|
||||
[errors]
|
||||
);
|
||||
|
||||
if (!isDirty && !(submissionAttempts > 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!errorList.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ul className="mt-1 space-y-1 text-sm text-destructive">
|
||||
{errorList.map((error, index) => {
|
||||
if (typeof error === "string") {
|
||||
return (
|
||||
<li key={index} className="flex items-center space-x-2">
|
||||
<AlertCircle
|
||||
size={16}
|
||||
className="flex-shrink-0 text-destructive"
|
||||
/>
|
||||
<span>{error}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<li key={index} className="flex flex-col space-y-0.5">
|
||||
<div className="flex items-center space-x-2">
|
||||
<AlertCircle
|
||||
size={16}
|
||||
className="flex-shrink-0 text-destructive"
|
||||
/>
|
||||
<span>{error.message}</span>
|
||||
</div>
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
{errorList.map((error, index) => (
|
||||
<li key={`${index}-${error}`} className="flex items-center space-x-2">
|
||||
<AlertCircle size={16} className="flex-shrink-0 text-destructive" />
|
||||
<span>{error}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
import * as LabelPrimitive from "@radix-ui/react-label";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import * as React from "react";
|
||||
import {
|
||||
Controller,
|
||||
type ControllerProps,
|
||||
type FieldPath,
|
||||
type FieldValues,
|
||||
FormProvider,
|
||||
useFormContext,
|
||||
useFormState,
|
||||
} from "react-hook-form";
|
||||
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { cn } from "@/presentation/utils";
|
||||
|
||||
const Form = FormProvider;
|
||||
|
||||
type FormFieldContextValue<
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
> = {
|
||||
name: TName;
|
||||
};
|
||||
|
||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||
{} as FormFieldContextValue
|
||||
);
|
||||
|
||||
const FormField = <
|
||||
TFieldValues extends FieldValues = FieldValues,
|
||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
||||
>({
|
||||
...props
|
||||
}: ControllerProps<TFieldValues, TName>) => {
|
||||
return (
|
||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||
<Controller {...props} />
|
||||
</FormFieldContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
const useFormField = () => {
|
||||
const fieldContext = React.useContext(FormFieldContext);
|
||||
const itemContext = React.useContext(FormItemContext);
|
||||
const { getFieldState } = useFormContext();
|
||||
const formState = useFormState({ name: fieldContext.name });
|
||||
const fieldState = getFieldState(fieldContext.name, formState);
|
||||
|
||||
if (!fieldContext) {
|
||||
throw new Error("useFormField should be used within <FormField>");
|
||||
}
|
||||
|
||||
const { id } = itemContext;
|
||||
|
||||
return {
|
||||
id,
|
||||
name: fieldContext.name,
|
||||
formItemId: `${id}-form-item`,
|
||||
formDescriptionId: `${id}-form-item-description`,
|
||||
formMessageId: `${id}-form-item-message`,
|
||||
...fieldState,
|
||||
};
|
||||
};
|
||||
|
||||
type FormItemContextValue = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||
{} as FormItemContextValue
|
||||
);
|
||||
|
||||
function FormItem({ className, ...props }: React.ComponentProps<"div">) {
|
||||
const id = React.useId();
|
||||
|
||||
return (
|
||||
<FormItemContext.Provider value={{ id }}>
|
||||
<div
|
||||
data-slot="form-item"
|
||||
className={cn("grid gap-2", className)}
|
||||
{...props}
|
||||
/>
|
||||
</FormItemContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
function FormLabel({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
||||
const { error, formItemId } = useFormField();
|
||||
|
||||
return (
|
||||
<Label
|
||||
data-slot="form-label"
|
||||
data-error={!!error}
|
||||
className={cn("data-[error=true]:text-destructive", className)}
|
||||
htmlFor={formItemId}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FormControl({ ...props }: React.ComponentProps<typeof Slot>) {
|
||||
const { error, formItemId, formDescriptionId, formMessageId } =
|
||||
useFormField();
|
||||
|
||||
return (
|
||||
<Slot
|
||||
data-slot="form-control"
|
||||
id={formItemId}
|
||||
aria-describedby={
|
||||
error ? `${formDescriptionId} ${formMessageId}` : `${formDescriptionId}`
|
||||
}
|
||||
aria-invalid={!!error}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
|
||||
const { formDescriptionId } = useFormField();
|
||||
|
||||
return (
|
||||
<p
|
||||
data-slot="form-description"
|
||||
id={formDescriptionId}
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
|
||||
const { error, formMessageId } = useFormField();
|
||||
const body = error ? String(error?.message ?? "") : props.children;
|
||||
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<p
|
||||
data-slot="form-message"
|
||||
id={formMessageId}
|
||||
className={cn("text-destructive text-sm", className)}
|
||||
{...props}
|
||||
>
|
||||
{body}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
export {
|
||||
useFormField,
|
||||
Form,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormMessage,
|
||||
FormField,
|
||||
};
|
||||
Reference in New Issue
Block a user