No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Curso Profesional de Next.js

Curso Profesional de Next.js

Oscar Barajas Tavares

Oscar Barajas Tavares

Construcci贸n del modal para crear productos

20/31
Recursos

Aportes 10

Preguntas 2

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

No se si estaba antes en el repo pero no vi que estuviera el componente FormProduct, cuando se nos pasaron las vistas, igual a qui lo dejo como inicial si a alguien le sirve.

export default function FormProduct() {
  return (
    <form>
      <div className="overflow-hidden">
        <div className="px-4 py-5 bg-white sm:p-6">
          <div className="grid grid-cols-6 gap-6">
            <div className="col-span-6 sm:col-span-3">
              <label
                htmlFor="title"
                className="block text-sm font-medium text-gray-700"
              >
                Title
              </label>
              <input
                type="text"
                name="title"
                id="title"
                className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
              />
            </div>
            <div className="col-span-6 sm:col-span-3">
              <label
                htmlFor="price"
                className="block text-sm font-medium text-gray-700"
              >
                Price
              </label>
              <input
                type="number"
                name="price"
                id="price"
                className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
              />
            </div>
            <div className="col-span-6">
              <label
                htmlFor="category"
                className="block text-sm font-medium text-gray-700"
              >
                Category
              </label>
              <select
                id="category"
                name="category"
                autoComplete="category-name"
                className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
              >
                <option value="1">Clothes</option>
                <option value="2">Electronics</option>
                <option value="3">Furniture</option>
                <option value="4">Toys</option>
                <option value="5">Others</option>
              </select>
            </div>

            <div className="col-span-6">
              <label
                htmlFor="description"
                className="block text-sm font-medium text-gray-700"
              >
                Description
              </label>
              <textarea
                name="description"
                id="description"
                autoComplete="description"
                rows="3"
                className="form-textarea mt-1 block w-full mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
              />
            </div>
            <div className="col-span-6">
              <div>
                <label className="block text-sm font-medium text-gray-700">
                  Cover photo
                </label>
                <div className="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
                  <div className="space-y-1 text-center">
                    <svg
                      className="mx-auto h-12 w-12 text-gray-400"
                      stroke="currentColor"
                      fill="none"
                      viewBox="0 0 48 48"
                      aria-hidden="true"
                    >
                      <path
                        d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
                        strokeWidth={2}
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      />
                    </svg>
                    <div className="flex text-sm text-gray-600">
                      <label
                        htmlFor="images"
                        className="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
                      >
                        <span>Upload a file</span>
                        <input
                          id="images"
                          name="images"
                          type="file"
                          className="sr-only"
                        />
                      </label>
                      <p className="pl-1">or drag and drop</p>
                    </div>
                    <p className="text-xs text-gray-500">
                      PNG, JPG, GIF up to 10MB
                    </p>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div className="px-4 py-3 bg-gray-50 text-right sm:px-6">
          <button
            type="submit"
            className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
          >
            Save
          </button>
        </div>
      </div>
    </form>
  );
}

Clase #20: Construcci贸n del modal para crear productos 20/31 馃敡


Continuando con el proyecto: 馃敤


Dado que en VSC no tenemos el archivo por defecto de FormProduct.js de la ruta src/components podemos crear el archivo y el contenido copiarlo directamente del repositorio del profesor (enlace: aqu铆). La estructura de FormProduct.js antes del return queda:

import { useRef } from 'react';
//useRef nos permite hacer una referencia al formulario a formData

export default function FormProduct() {
  const formRef = useRef(null); //null como primera instancia

  const handleSubmit = (event) => {
    event.preventDefault();
    //formData captura cada elemento del input
    const formData = new FormData(formRef.current);
    //Destructurar los datos
    const data = {
      title: formData.get('title'), //Se pasa el nombre del elemento
      price: parseInt(formData.get('price')), //Se transforma en valor num茅rico
      description: formData.get('description'),
      categoryId: parseInt(formData.get('category')),
      images: [formData.get('images').name],
    };
    console.log(data);
  };
  //Con formRef permite conectar al formulario
  //Con handleSubmit nos permite obtener la informaci贸n


Seg煤n la documentaci贸n de TailwindUI (enlace: aqu铆) se debe instalar la dependencia @tailwindcss/forms para ello en consola se ejecuta:

npm install @tailwindcss/forms


En el archivo tailwind.config.js se agrega el plugin de @tailwindcss/forms:

plugins: [
require('@tailwindcss/forms'),
],


En el archivo products.js de la ruta src/pages/dashboard se agrega el import de FormProduct.js:

import FormProduct from '@components/FormProduct';


Dentro de la etiqueta de Modal se implementa la etiqueta <FormProduct /> de forma que cuando se haga click en 鈥Add Product鈥 aparezca el formulario para agregar los datos correspondientes al producto:

<Modal open={open} setOpen={setOpen}>
<FormProduct />
</Modal>


Guardamos todo y ejecutamos en consola npm run dev

Para la validaci贸n, lo hice con yup, en FormProduct.js agregue la validacion del esquema al handleSubmit:

const handleSubmit = async (e) => {
    e.preventDefault();
    const formData = new FormData(formRef.current);
    const data = {
      title: formData.get('title'),
      price: parseInt(formData.get('price')),
      description: formData.get('description'),
      categoryId: parseInt(formData.get('category')),
      images: [formData.get('images').name],
    };
    console.log(data);
    const validation = await ProductSchema.validate(data);
    console.log({ validation });
  };

y el ProductSchema.js :

import * as Yup from 'yup';

const ProductSchema = Yup.object().shape({
  title: Yup.string().min(3).max(40).required(),
  price: Yup.number().min(5).max(10000).required(),
  description: Yup.string().min(3).max(180).required(),
  categoryId: Yup.number().required(),
  images: Yup.array().of(Yup.string()),
});

export { ProductSchema };

Saludos!

Para la validaci贸n utilize react-hook-form junto con joi, para empezar necesitamos instalar las dependencias:

npm i react-hook-form joi @hookform/resolvers/joi

luego cree el esquema para la validaci贸n de los datos:


const imageExtentions = ['image/png', 'image/jpg', 'image/jpeg'];

const ValidationSchema = Joi.object({
  title: Joi.string().min(5).required(),
  price: Joi.number().greater(0).precision(2).required(),
  category: Joi.string().valid('1', '2', '3', '4', '5').required(),
  description: Joi.string().required(),
  images: Joi.any()
    .custom(function (file, { error }) {
      if (!file[0]) {
        return error('file.required');
      }

      if (!imageExtentions.includes(file[0].type)) {
        return error('file.invalid');
      }

      return file;
    })
    .messages({
      'file.required': 'File is required',
      'file.invalid': 'The file must be one of the following png, jpg or jpeg.',
    }),
});

El codigo completo queda de la siguiente manera:

import { useRef } from 'react';
import { useForm } from 'react-hook-form';
import * as Joi from 'joi';
import { joiResolver } from '@hookform/resolvers/joi';

type Inputs = {
  title: string;
  price: string;
  category: number;
  description: string;
  images: FileList;
};

const imageExtentions = ['image/png', 'image/jpg', 'image/jpeg'];

const ValidationSchema = Joi.object({
  title: Joi.string().min(5).required(),
  price: Joi.number().greater(0).precision(2).required(),
  category: Joi.string().valid('1', '2', '3', '4', '5').required(),
  description: Joi.string().required(),
  images: Joi.any()
    .custom(function (file, { error }) {
      if (!file[0]) {
        return error('file.required');
      }

      if (!imageExtentions.includes(file[0].type)) {
        return error('file.invalid');
      }

      return file;
    })
    .messages({
      'file.required': 'File is required',
      'file.invalid': 'The file must be one of the following png, jpg or jpeg.',
    }),
});

export default function FormProduct() {
  const formRef = useRef(null);
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm<Inputs>({
    resolver: joiResolver(ValidationSchema),
  });

  const onSubmit = (d) => {
    console.log(d);
  };

  return (
    <form ref={formRef} onSubmit={handleSubmit(onSubmit)}>
      <div className="overflow-hidden">
        <div className="px-4 py-5 bg-white sm:p-6">
          <div className="grid grid-cols-6 gap-6">
            <div className="col-span-6 sm:col-span-3">
              <label
                htmlFor="title"
                className="block text-sm font-medium text-gray-700"
              >
                Title
              </label>
              <input
                type="text"
                id="title"
                className={`mt-1 
                    focus:ring-indigo-500 
                    focus:border-indigo-500 
                    block w-full shadow-sm 
                    sm:text-sm border-gray-300 
                    rounded-md
                    ${errors.title && 'border-red-300'}`}
                {...register('title')}
              />
              {errors.title && (
                <span className="text-red-400 font-thin text-sm">
                  {errors.title.message}
                </span>
              )}
            </div>
            <div className="col-span-6 sm:col-span-3">
              <label
                htmlFor="price"
                className="block text-sm font-medium text-gray-700"
              >
                Price
              </label>
              <input
                type="number"
                {...register('price')}
                id="price"
                className={`mt-1 
                    focus:ring-indigo-500 
                    focus:border-indigo-500 
                    block w-full shadow-sm sm:text-sm 
                    border-gray-300 rounded-md 
                    ${errors.price && 'border-red-300'}`}
              />
              {errors.price && (
                <span className="text-red-400 font-thin text-sm">
                  {errors.price.message}
                </span>
              )}
            </div>
            <div className="col-span-6">
              <label
                htmlFor="category"
                className="block text-sm font-medium text-gray-700"
              >
                Category
              </label>
              <select
                id="category"
                {...register('category')}
                autoComplete="category-name"
                className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
              >
                <option value="1">Clothes</option>
                <option value="2">Electronics</option>
                <option value="3">Furniture</option>
                <option value="4">Toys</option>
                <option value="5">Others</option>
              </select>
            </div>

            <div className="col-span-6">
              <label
                htmlFor="description"
                className="block text-sm font-medium text-gray-700"
              >
                Description
              </label>
              <textarea
                {...register('description')}
                id="description"
                autoComplete="description"
                rows={3}
                className={`form-textarea
                     mt-1 block w-full mt-1 
                     focus:ring-indigo-500 
                     focus:border-indigo-500 block w-full 
                     shadow-sm sm:text-sm 
                     border-gray-300 rounded-md
                     ${errors.description && 'border-red-300'}`}
              />
              {errors.description && (
                <span className="text-red-400 font-thin text-sm">
                  {errors.description.message}
                </span>
              )}
            </div>
            <div className="col-span-6">
              <div>
                <label className="block text-sm font-medium text-gray-700">
                  Cover photo
                </label>
                <div className="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
                  <div className="space-y-1 text-center">
                    <svg
                      className="mx-auto h-12 w-12 text-gray-400"
                      stroke="currentColor"
                      fill="none"
                      viewBox="0 0 48 48"
                      aria-hidden="true"
                    >
                      <path
                        d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
                        strokeWidth={2}
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      />
                    </svg>
                    <div className="flex text-sm text-gray-600">
                      <label
                        htmlFor="images"
                        className="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
                      >
                        <span>Upload a file</span>
                        <input
                          id="images"
                          {...register('images')}
                          type="file"
                          className="sr-only"
                        />
                      </label>
                      <p className="pl-1">or drag and drop</p>
                    </div>
                    <p className="text-xs text-gray-500">
                      PNG, JPG, GIF up to 10MB
                    </p>
                  </div>
                </div>
                {errors.images && (
                  <span className="text-red-400 font-thin text-sm text-center w-full">
                    {errors.images.message}
                  </span>
                )}
              </div>
            </div>
          </div>
        </div>
        <div className="px-4 py-3 bg-gray-50 text-right sm:px-6">
          <button
            type="submit"
            className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
          >
            Save
          </button>
        </div>
      </div>
    </form>
  );
}

Estuve viendo que algunos resolvieron el reto usando dependencias.
Yo lo resolv铆 usando Expresiones Regulares.
La ventaja de esto es que no hay que instalar nada.
.
Primero tenemos una funci贸n checkData(data) que se encarga de ver si hay datos inv谩lidos. Esta funci贸n devuelve true o false dependiendo de si todos los datos son v谩lidos o no respectivamente.

  • checkData(data)
  function checkData(data) {  // las REGEX se aplican sobre Strings (toString())
    let pass = true;
    if (!data.title.match(/\w{5,}/g)) {
      alert('Title must be at least 5 characters');
      pass = false;
    } if (!(data.price).toString().match(/^[0-9]+$/g)) {
      alert('Invalid price');
      pass = false;
    } if (!data.description.match(/\w{5,}/g)) {
      alert('Description must be at least 5 characters');
      pass = false;
    } if (!data.images[0].match(/^.+\.(jpg|jpeg|png)$/g)) { //verifica la extensi贸n
      alert('Invalid file extension');
      pass = false;
    }

    return pass;
  }

.
Y dentro de la funci贸n handleSubmit() le enviamos 鈥渄ata鈥 a checkData(). Si obtenemos 鈥渢rue鈥, significa que los datos ingresados son v谩lidos, caso contrario recibiremos una alerta con el mensaje correspondiente.

  function handleSubmit(event) {
    event.preventDefault();
    const formData = new FormData(formRef.current)
    const data = {
      "title": formData.get('title'),
      "price": parseInt(formData.get('price')),
      "description": formData.get('description'),
      "categoryId": parseInt(formData.get('category')),
      "images": [formData.get('images').name],
    }
    const passedCheck = checkData(data);
    // console.log(passedCheck);
    if (passedCheck) {
      //  ...
    }
  }

.
Se podr铆a agregar m谩s l贸gica. Podr铆amos agregar estados de React independientes para cada 铆tem y as铆 tener control sobre lo que se renderiza.

Para la validaci贸n estuve probando React Hook Form y la verdad que bastante sencillo y muy potente

import { useForm } from 'react-hook-form';

export default function FormProduct() {
  const {register, handleSubmit, formState: { errors },} = useForm();

  const onSubmit = (data) => {
    console.log(data);
  };

  return (
	<form onSubmit={handleSubmit(onSubmit)}>
	....

	<div className="col-span-6 sm:col-span-3">
		<label htmlFor="title" className="block text-sm font-medium text-gray-700">Title</label>
		<input {...register('title', { required: true })} type="text" id="title" className={`${errors.title && `border-red-500`} mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md`} />
              {errors.title?.type === 'required' && <p className="text-red-500 text-xs italic">Title is required</p>}
	</div>

	....
	</form>
  );
}

instalamos dependencias

npm install @tailwindcss/forms

a帽adimos la configuraci贸n

/** @type {import('tailwindcss').Config} */

const colors = require("tailwindcss/colors") 

module.exports = {
  content: ["./src/**/*{html,js,jsx}"], 
  theme: {
    extend: { 
      color: {
        ...colors,
      },
    }, 
  },
  plugins: [ 
    require('@tailwindcss/forms'),
  ], 
};

creamos el componente FormProduct
src/components/FormProduct.jsx:

import { useRef } from "react";

export default function FormProduct() {
    const formRef = useRef(null);    

    const handleSubmit = (e)=> {
        e.preventDefault();
        const formData = new FormData(formRef.current);
      
        const data = {
          "title": formData.get("title"),
          "price": parseInt(formData.get("price")),
          "description": formData.get("description"),
          "categoryId": parseInt(formData.get("category")),
          "images": [
            formData.get("images").name,
          ]
        }
        
        console.log(data);
    }
    return (
      <form ref={formRef} onSubmit={handleSubmit}>
        <div className="overflow-hidden">
          <div className="px-4 py-5 bg-white sm:p-6">
            <div className="grid grid-cols-6 gap-6">
              <div className="col-span-6 sm:col-span-3">
                <label
                  htmlFor="title"
                  className="block text-sm font-medium text-gray-700"
                >
                  Title
                </label>
                <input
                  type="text"
                  name="title"
                  id="title"
                  className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
                />
              </div>
              <div className="col-span-6 sm:col-span-3">
                <label
                  htmlFor="price"
                  className="block text-sm font-medium text-gray-700"
                >
                  Price
                </label>
                <input
                  type="number"
                  name="price"
                  id="price"
                  className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
                />
              </div>
              <div className="col-span-6">
                <label
                  htmlFor="category"
                  className="block text-sm font-medium text-gray-700"
                >
                  Category
                </label>
                <select
                  id="category"
                  name="category"
                  autoComplete="category-name"
                  className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
                >
                  <option value="1">Clothes</option>
                  <option value="2">Electronics</option>
                  <option value="3">Furniture</option>
                  <option value="4">Toys</option>
                  <option value="5">Others</option>
                </select>
              </div>
  
              <div className="col-span-6">
                <label
                  htmlFor="description"
                  className="block text-sm font-medium text-gray-700"
                >
                  Description
                </label>
                <textarea
                  name="description"
                  id="description"
                  autoComplete="description"
                  rows="3"
                  className="form-textarea mt-1 block w-full mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
                />
              </div>
              <div className="col-span-6">
                <div>
                  <label className="block text-sm font-medium text-gray-700">
                    Cover photo
                  </label>
                  <div className="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
                    <div className="space-y-1 text-center">
                      <svg
                        className="mx-auto h-12 w-12 text-gray-400"
                        stroke="currentColor"
                        fill="none"
                        viewBox="0 0 48 48"
                        aria-hidden="true"
                      >
                        <path
                          d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
                          strokeWidth={2}
                          strokeLinecap="round"
                          strokeLinejoin="round"
                        />
                      </svg>
                      <div className="flex text-sm text-gray-600">
                        <label
                          htmlFor="images"
                          className="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
                        >
                          <span>Upload a file</span>
                          <input
                            id="images"
                            name="images"
                            type="file"
                            className="sr-only"
                          />
                        </label>
                        <p className="pl-1">or drag and drop</p>
                      </div>
                      <p className="text-xs text-gray-500">
                        PNG, JPG, GIF up to 10MB
                      </p>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div className="px-4 py-3 bg-gray-50 text-right sm:px-6">
            <button
              type="submit"
              className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
            >
              Save
            </button>
          </div>
        </div>
      </form>
    );
}

a帽adimos FormProduct al modal
src/pages/dashboard/products.js:

import FormProduct from "@components/FormProduct";

export default function Products(){
    const [open, setOpen] = useState(false);
    const [products, setProducts] = useState([]);

    return (
        <>
           ....
            <Modal 
                open={open}
                setOpen={setOpen}
            >
                <FormProduct /> 
            </Modal>
        </>
    )
}

Podriamos ponerle un m铆nimo al input de precio para que no se a帽adan n煤meros negativos.

Por ahora solo agregu茅 un required en cada uno de los inputs. Y cree una nueva opci贸n en el selector para que no estuviera seleccionado ninguno por defecto.

Agregu茅 al selector:

required
defaultValue="0"

Y cree una opci贸n por defecto que no se pueda seleccionar.

<option value="0" disabled> --Select-- </option>

Quedando as铆:

Para el reto, use Joi como validador.

npm i joi

Quedando el archivo FormProducts.js de esta manera:

import { useRef } from 'react';
import * as Joi from 'joi';

const ProductSchema = Joi.object({
  title: Joi.string().min(5).required(),
  price: Joi.number().integer().greater(0).precision(0).required(),
  categoryId: Joi.number().valid(1, 2, 3, 4, 5).required(),
  description: Joi.string().required(),
  images: Joi.array().items(Joi.string().pattern(new RegExp('/^.+.(jpg|jpeg|png)$/g'))),
});

export default function FormProduct() {
  const formRef = useRef(null);

  const handleSubmit = (event) => {
    event.preventDefault();
    const formData = new FormData(formRef.current);
    const data = {
      title: formData.get('title'),
      price: parseInt(formData.get('price')),
      description: formData.get('description'),
      categoryId: parseInt(formData.get('category')),
      images: [formData.get('images').name],
    };
    const { error, value } = ProductSchema.validate(data);
    console.log(value);
    console.log(error);
  };

  return (
    <form ref={formRef} onSubmit={handleSubmit}>
      <div className="overflow-hidden">
        <div className="px-4 py-5 bg-white sm:p-6">
          <div className="grid grid-cols-6 gap-6">
            <div className="col-span-6 sm:col-span-3">
              <label htmlFor="title" className="block text-sm font-medium text-gray-700">
                Title
              </label>
              <input type="text" name="title" id="title" className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" />
            </div>
            <div className="col-span-6 sm:col-span-3">
              <label htmlFor="price" className="block text-sm font-medium text-gray-700">
                Price
              </label>
              <input type="number" name="price" id="price" className="mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md" />
            </div>
            <div className="col-span-6">
              <label htmlFor="category" className="block text-sm font-medium text-gray-700">
                Category
              </label>
              <select
                id="category"
                name="category"
                autoComplete="category-name"
                className="mt-1 block w-full py-2 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"
              >
                <option value="1">Clothes</option>
                <option value="2">Electronics</option>
                <option value="3">Furniture</option>
                <option value="4">Toys</option>
                <option value="5">Others</option>
              </select>
            </div>

            <div className="col-span-6">
              <label htmlFor="description" className="block text-sm font-medium text-gray-700">
                Description
              </label>
              <textarea
                name="description"
                id="description"
                autoComplete="description"
                rows="3"
                className="form-textarea mt-1 block w-full mt-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full shadow-sm sm:text-sm border-gray-300 rounded-md"
              />
            </div>
            <div className="col-span-6">
              <div>
                <label className="block text-sm font-medium text-gray-700">Cover photo</label>
                <div className="mt-1 flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
                  <div className="space-y-1 text-center">
                    <svg className="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
                      <path
                        d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
                        strokeWidth={2}
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      />
                    </svg>
                    <div className="flex text-sm text-gray-600">
                      <label
                        htmlFor="images"
                        className="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
                      >
                        <span>Upload a file</span>
                        <input id="images" name="images" type="file" className="sr-only" />
                      </label>
                      <p className="pl-1">or drag and drop</p>
                    </div>
                    <p className="text-xs text-gray-500">PNG, JPG, GIF up to 10MB</p>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
        <div className="px-4 py-3 bg-gray-50 text-right sm:px-6">
          <button
            type="submit"
            className="inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
          >
            Save
          </button>
        </div>
      </div>
    </form>
  );
}