EmpiezaTuSaaS

Formularios

Inputs, validación y manejo de formularios

Formularios

El boilerplate incluye los componentes Input, Label y Form de shadcn/ui. El componente Form integra React Hook Form con Zod para validación.

Componentes incluidos

Los siguientes componentes ya estan instalados en components/ui/:

  • Input (input.tsx) - Campo de texto
  • Label (label.tsx) - Etiqueta para inputs
  • Form (form.tsx) - Formulario con React Hook Form y validación

Input básico

import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";

<div className="space-y-2">
  <Label htmlFor="email">Email</Label>
  <Input
    id="email"
    type="email"
    placeholder="tu@email.com"
  />
</div>

Formulario con validación

El componente Form usa React Hook Form con zodResolver para validación:

Ejemplo: formulario de contacto
"use client";

import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import {
  Form,
  FormControl,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "@/components/ui/form";

const formSchema = z.object({
  name: z.string().min(2, "El nombre debe tener al menos 2 caracteres"),
  email: z.string().email("Email inválido"),
  message: z.string().min(10, "El mensaje debe tener al menos 10 caracteres"),
});

type FormValues = z.infer<typeof formSchema>;

export function ContactForm() {
  const form = useForm<FormValues>({
    resolver: zodResolver(formSchema),
    defaultValues: {
      name: "",
      email: "",
      message: "",
    },
  });

  const onSubmit = async (data: FormValues) => {
    console.log(data);
    // Enviar datos
  };

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
        <FormField
          control={form.control}
          name="name"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Nombre</FormLabel>
              <FormControl>
                <Input placeholder="Tu nombre" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <FormField
          control={form.control}
          name="email"
          render={({ field }) => (
            <FormItem>
              <FormLabel>Email</FormLabel>
              <FormControl>
                <Input type="email" placeholder="tu@email.com" {...field} />
              </FormControl>
              <FormMessage />
            </FormItem>
          )}
        />

        <Button type="submit" disabled={form.formState.isSubmitting}>
          {form.formState.isSubmitting ? "Enviando..." : "Enviar"}
        </Button>
      </form>
    </Form>
  );
}

Formularios en el boilerplate

El boilerplate usa formularios en varios lugares:

Formulario de waitlist

El componente WaitlistHero incluye un formulario que hace POST a /api/lead:

components/landing/waitlist-hero.tsx
const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  setIsLoading(true);

  try {
    const response = await fetch("/api/lead", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ email, source: "waitlist" }),
    });

    if (!response.ok) throw new Error();
    setJoined(true);
    toast.success("¡Te has unido a la lista de espera!");
  } catch {
    toast.error("Error al unirte. Inténtalo de nuevo.");
  } finally {
    setIsLoading(false);
  }
};

<form onSubmit={handleSubmit} className="flex flex-col sm:flex-row gap-3 max-w-md mx-auto">
  <Input
    type="email"
    placeholder="tu@email.com"
    value={email}
    onChange={(e) => setEmail(e.target.value)}
    required
    disabled={isLoading}
    className="flex-1"
  />
  <Button type="submit" disabled={isLoading}>
    {isLoading ? (
      <Loader2 className="h-4 w-4 animate-spin" />
    ) : (
      <>
        Unirme <ArrowRight className="ml-1 h-4 w-4" />
      </>
    )}
  </Button>
</form>

Notificaciones con Sonner

El boilerplate usa sonner (no react-hot-toast) para notificaciones toast:

import { toast } from "sonner";

toast.success("¡Te has unido a la lista de espera!");
toast.error("Error al unirte. Inténtalo de nuevo.");
toast.error("Error al procesar el pago. Inténtalo de nuevo.");

Agregar mas componentes de formulario

Puedes añadir mas componentes de formulario con shadcn:

npx shadcn@latest add select
npx shadcn@latest add checkbox
npx shadcn@latest add textarea
npx shadcn@latest add radio-group
npx shadcn@latest add switch

On this page