login form created

This commit is contained in:
2026-03-18 19:14:08 -05:00
parent e025d0f5cc
commit 6b8d7b53d0
20 changed files with 800 additions and 73 deletions

View File

@@ -0,0 +1,33 @@
import { Label } from "@radix-ui/react-label";
import { Checkbox } from "@/components/ui/checkbox";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
type CheckboxFieldProps = {
label: string;
description?: string;
};
export const CheckboxField = ({ label }: CheckboxFieldProps) => {
const field = useFieldContext<boolean>();
return (
<div>
<div className="flex items-center gap-3">
<Label htmlFor={field.name}>
<span>{label}</span>
</Label>
<Checkbox
id={field.name}
checked={field.state.value}
onCheckedChange={(checked) => {
field.handleChange(checked === true);
}}
onBlur={field.handleBlur}
/>
</div>
<FieldErrors meta={field.state.meta} />
</div>
);
};

View File

@@ -0,0 +1,18 @@
import type { AnyFieldMeta } from "@tanstack/react-form";
type FieldErrorsProps = {
meta: AnyFieldMeta;
};
export const FieldErrors = ({ meta }: FieldErrorsProps) => {
if (!meta.isTouched) return null;
return meta.errors.map((error) => (
<p
key={`${error.message}-${error.code ?? "err"}`}
className="text-sm font-medium text-destructive"
>
{error.message}
</p>
));
};

View File

@@ -0,0 +1,37 @@
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
type InputFieldProps = {
label: string;
inputType: string;
required: boolean;
};
const autoCompleteMap: Record<string, string> = {
email: "email",
password: "current-password",
text: "off",
username: "username",
};
export const InputField = ({ label, inputType, required }: InputFieldProps) => {
const field = useFieldContext<any>();
return (
<div className="grid gap-3">
<Label htmlFor={field.name}>{label}</Label>
<Input
id={field.name}
autoComplete={autoCompleteMap[inputType] ?? "off"}
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
type={inputType}
required={required}
/>
<FieldErrors meta={field.state.meta} />
</div>
);
};

View File

@@ -0,0 +1,49 @@
import { Eye, EyeOff } from "lucide-react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { useFieldContext } from ".";
import { FieldErrors } from "./Errors.Field";
type PasswordInputProps = {
label: string;
required?: boolean;
};
export const InputPasswordField = ({
label,
required = false,
}: PasswordInputProps) => {
const field = useFieldContext<any>();
const [show, setShow] = useState(false);
return (
<div className="grid gap-3 mt-2">
<Label htmlFor={field.name}>{label}</Label>
<div className="relative">
<Input
id={field.name}
type={show ? "text" : "password"}
autoComplete="current-password"
value={field.state.value}
onChange={(e) => field.handleChange(e.target.value)}
onBlur={field.handleBlur}
required={required}
className="pr-10"
/>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => setShow((p) => !p)}
className="absolute right-1 top-1/2 -translate-y-1/2 h-7 w-7"
tabIndex={-1}
>
{show ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</Button>
</div>
<FieldErrors meta={field.state.meta} />
</div>
);
};

View File

@@ -0,0 +1,24 @@
import { useStore } from "@tanstack/react-form";
import { Button } from "@/components/ui/button";
import { useFormContext } from ".";
type SubmitButtonProps = {
children: React.ReactNode;
};
export const SubmitButton = ({ children }: SubmitButtonProps) => {
const form = useFormContext();
const [isSubmitting] = useStore(form.store, (state) => [
state.isSubmitting,
state.canSubmit,
]);
return (
<div className="">
<Button type="submit" disabled={isSubmitting}>
{children}
</Button>
</div>
);
};

View File

@@ -0,0 +1,23 @@
import { createFormHook, createFormHookContexts } from "@tanstack/react-form";
import { CheckboxField } from "./CheckBox.Field";
import { InputField } from "./Input.Field";
import { InputPasswordField } from "./InputPassword.Field";
import { SubmitButton } from "./SubmitButton";
export const { fieldContext, useFieldContext, formContext, useFormContext } =
createFormHookContexts();
export const { useAppForm } = createFormHook({
fieldComponents: {
InputField,
InputPasswordField,
//SelectField,
CheckboxField,
//DateField,
//TextArea,
//Searchable,
},
formComponents: { SubmitButton },
fieldContext,
formContext,
});