diff options
author | 2025-04-24 09:13:07 +0530 | |
---|---|---|
committer | 2025-04-24 09:13:07 +0530 | |
commit | caace928ac81c284629ee50942d72179d4da9784 (patch) | |
tree | b2f4e87b7a53e30ac5ac9af94cdc70c2da5bbfb9 /frontend/src/components/ui/form.tsx | |
parent | 50d5e6534f5e593297a09323e683c7c8b850117b (diff) | |
download | finance-caace928ac81c284629ee50942d72179d4da9784.tar.gz finance-caace928ac81c284629ee50942d72179d4da9784.tar.bz2 finance-caace928ac81c284629ee50942d72179d4da9784.zip |
feat: Fix loan API type assertion and complete core loan features
- Resolve interface conversion panic in loan handlers by correcting
user type assertions from *models.User to models.User
- Finalize loan management API integration with frontend components
- Implement remaining loan calculation logic and CRUD operations
- Connect loan display components to backend APIs as per Phase 3
- Update project status in README.md to reflect completed loan features
- Add CORS middleware configuration for frontend-backend communication
This commit completes core loan management functionality and fixes critical
type safety issues in the API handlers, enabling proper user context handling.
Diffstat (limited to 'frontend/src/components/ui/form.tsx')
-rw-r--r-- | frontend/src/components/ui/form.tsx | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/frontend/src/components/ui/form.tsx b/frontend/src/components/ui/form.tsx new file mode 100644 index 0000000..524b986 --- /dev/null +++ b/frontend/src/components/ui/form.tsx @@ -0,0 +1,167 @@ +"use client" + +import * as React from "react" +import * as LabelPrimitive from "@radix-ui/react-label" +import { Slot } from "@radix-ui/react-slot" +import { + Controller, + FormProvider, + useFormContext, + useFormState, + type ControllerProps, + type FieldPath, + type FieldValues, +} from "react-hook-form" + +import { cn } from "@/lib/utils" +import { Label } from "@/components/ui/label" + +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}` + : `${formDescriptionId} ${formMessageId}` + } + 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, +} |