Skip to main content
  1. All Posts/

Teslo

eCommerce TypeScript

Teslo: Readme

Autor: Anselmo Tomas Cuevas
Ultima actualización: 2022-12-6

Contenido

  • Objetivo
  • Metas
  • Background
  • Diseño detallado

    Objetivo

    Proyecto personal de Anselmo Tomas Cuevas, creando un Ecommerce, con el fin de aprender NextJs junto con librerias extras como swr, NextAuth, MaterialUI, Cloudinary, Tailwind, Axios, etc. Utilizando MongoDB como base de datos y mongoose como ORM.
    ⬆️ Volver al indice

    Metas

    • Pagina de autenticacion (login y register).
    • Pagina principal, donde se listen todos los productos que existan en la base de datos.
    • Pagina por categoria (hombres, mujeres, niños).
    • Pagina dedicada a cada producto.
    • Elegir cantidad de items, talla de producto y agregar producto al carrito.
    • Pagina de carrito donde se listen todos los productos del mismo y el total a pagar.
    • Pagina para establecer la direccion y datos del usuario.
    • Pagina de resumen y confirmacion de la orden.
    • Pago con PayPal.
    • Pagina donde el usuario pueda ver todas sus ordenes.
    • Buscador de productos.
    • Dashboards administrativo.
    • Verificacion de autenticacion y autorizacion.
    • Verificacion de admin
    • Backend con apis para los clientes:
      • API de usuarios: Para registrarse y iniciar sesion.
      • API de productos: Para obtener todos los productos o individualmente.
      • API de ordenes: Para obtener todas las ordenes del usuario o individualmente. Crear y pagar orden.
      • API para realizar busquedas de productos.
    • Backend con apis para los administradores:
      • API de usuarios: Para obtener todos los usuarios de la aplicacion y poder cambiarle el role a los mismos.
      • API de productos: Para poder obtener todos los productos, actualizar o crear un nuevo producto.
      • API de ordenes: Para obtener todas las ordenes generadas en la aplicacion o individualmente.
      • API para obtener los datos para el dashboard principal.
      • API para poder subir las imagenes de los productos.

    ⬆️ Volver al indice

    Background

    Estuve aprendiendo NextJs, junto con demas herramientas, y me propuse a mejorar una aplicacion que realice en un curso tomado sobre Next. Mejorar el diseño, el performance y agregar funcionalidades extras, asi como la legibilidad del codigo y la estructura de los archivos.
    ⬆️ Volver al indice

    Diseño detallado

    Frontend

    Pagina de autenticacion


    Para la autenticacion, esta aplicacion utiliza la libreria de NextAuth. Esta ofrece multiples formas de realizar una autenticacion. Aqui, tendremos 2 formas de autenticacion.

    • Credenciales (email y password).
    • Github

    Credentials

    Cuando se autentica mediante las credenciales, tras dar en Ingresar, se llama a la funcion onLoginUser, a la cual, se le pasa el email y el password ingresado por el usuario. Dentro de esta funcion, se hace uso de la funcion signIn, proporcionada por NextAuth. Esta se encarga de realizar la autenticacion junto al backend de nuestra aplicacion.

    Github

    Para realizar el renderizado del boton de la autenticacion mediante Github, se debe utilizar la funcion getProviders, proporcionada por NextAuth, para obtener todos los providers configurados. Estos providers los establecemos en un estado local de nuestra pagina.

    const [providers, setProviders] = useState<any>({});
    
    getProviders().then((prov) => {
      setProviders(prov);
    });

    Tras tener todos los providers configurados, debemos renderizarlos en nuestra pagina de la siguiente manera:

    {
      Object.values(providers).map((provider: any) => {
        if (provider.id! === "credentials") return <div key={provider.id}></div>;
    
        return (
          <Button
            key={provider.id}
            variant="outlined"
            fullWidth
            color="primary"
            sx={{ mb: 1, borderRadius: "30px" }}
            onClick={() => signIn(provider.id)}
          >
            {provider.name}
          </Button>
        );
      });
    }

    Todos los providers que sean diferentes de credentials, devolveran un buton con el nombre del provider al cual pertenece. Al hacer click en tales botones, se ejecutara la funcion signin proporcionada por NextAuth.
    Al realizar el login con Github, NextAuth se encarga de la logica necesaria, obteniendo las credenciales desde Github y realizando la autenticacion con el backend de nuestra aplicacion.
    ⬆️ Volver al indice

    Pagina de registro


    Esta pagina, utiliza un context (AuthContext), para obtener el metodo onRegister, que se encarga de realizar la peticion hacia el backend, enviando los datos que el usuario ingreso en el formulario.

    const { onRegister } = useContext(AuthContext);

    El metodo onRegister se llama desde la funcion onLoginUser, que se encuentra en la misma pagina, cuando el usuario haya completado el formulario y haya hecho click en Registrarse.

    ⬆️ Volver al indice

    Pagina principal

    La pagina principal, hace uso de un CustomHook (useGetProducts) para obtener todos los productos desde la base de datos. Dentro de la pagina principal, hay un componente que se encarga de renderizar en pantalla todos estos productos.

    const { products } = useGetProducts("/products");



    ⬆️ Volver al indice

    Pagina por categoria

    Hay 3 categorias (hombres, mujeres y niños). Cada una de ellas tiene su propia pagina dedicada y todas funcionan igual.
    Las 3 paginas, al igual que la pagina principal, hacen uso del CustomHook (useGetProducts) para obtener los productos de la base de datos. Y luego, dentro de cada una de ellas, hay un componente encargado de renderizar los productos obtenidos.

    const { products } = useGetProducts("/products?gender=men");
    const { products } = useGetProducts("/products?gender=women");
    const { products } = useGetProducts("/products?gender=kid");


    ⬆️ Volver al indice

    Pagina de producto

    La pagina de producto, es generada de forma estatica utilizando la funcion getStaticProps de NextJs. Esta toma el slug que le llega por parametro y busca el producto en base al slug en la base de datos, utilizando una serverless function y genera la pagina estatica. La pagina, se revalidara cada 7 dias.

    const { slug = "" } = params as { slug: string };
    const product = await getProductBySlug(slug);
    
    return {
      props: {
        product,
      },
      revalidate: 60 * 60 * 24 * 7,
    };

    Cuando el usuario ingrese a la url del producto, el servidor le devolvera el html de el mismo.

    cambiar talla y cantidad de productos

    Dentro de esta pagina, hay un estado que se encarga de capturar la talla y la cantidad de items que el usuario seleccione.
    Hay 2 metodos que se encargan de modificar este estado. El primero, se encarga de cambiar la talla cuando el usuario haga click en la talla deseada y el segundo, en cambiar la cantidad de productos cuando el usuario cambie esta.

    const onSelectedSize = (size: IValidSizes) => {};
    
    const onSelectedQuantity = (add: boolean) => {};

    agregar al carrito

    Todos los productos que esten en el carrito de compras, se establecen en las cookies. Luego, hay un contexto que se encarga de manejar toda la logica de este. Este contexto posee metodos para obtener el carrito, agregar nuevo producto, actualizar cantidad de items, eliminar productos, actualizar direccion del usuario y crear una nueva orden.
    En la pagina del producto, obtendremos desde el contexto, el metodo para agregar un producto al carrito.

    const { addProductToCart } = useContext(CartContext);

    Cuando el usuario, haya elegido la talla y la cantidad de items y haga click en agregar al carrito, se llama este metodo. Este, tomara el producto junto con la talla y la cantidad de items seleccionados y lo establecera en el contexto, al cambiar el contexto, las cookies se actualizan con el nuevo cambio.
    Si el producto y la talla agregada ya se encontraban en el carrito, este, actualizara el producto que ya se encontraba en el contexto y tomara la cantidad de items que se encuentran en el contexto y le sumara la nueva cantidad.
    ⬆️ Volver al indice

    Pagina del carrito

    Se hace uso de CartContext para obtener la cantidad de items que hay almacenados en el carrito.

    const { numberOfItems } = useContext(CartContext);

    Dentro de esta pagina, hay un componente CartList encargado de renderizar todos los productos almacenados en el carrito. A este componente se le pasa el parametro editable={true}, para que dentro de este, se pueda editar la cantidad de items de cada producto o eliminar el producto del carrito si el usuario lo desea.

    <CardList editable={true} />

    Dentro de CartList, hacemos uso nuevamente del CartContext para tomar los productos almacenados en el y los metodos updateCartQuantity y deleteCart.

    const {
      cart: { cartItems },
      updateCartQuantity,
      deleteCart,
    } = useContext(CartContext);

    Y por ultimo, renderizamos todos los productos que se encuentren en el carrito.


    ⬆️ Volver al indice

    Pagina de direccion

    Esta pagina, utiliza el cartContext para actualizar los datos de la direccion y obtener el numero de items que hay en el carrito. Si el numeros de items en el carrito es 0, la pantalla no se renderiza y te envia a la pantalla principal.

    const { updateAddress, numberOfItems } = useContext(CartContext);


    Para el manejo del estado del formulario, se utiliza la libreria react-hook-form.

    const {
      register,
      handleSubmit,
      formState: { errors },
    } = useForm<IAddress>({
      defaultValues: getAddressFromCookies(),
    });

    Cuando el usuario click en revisar pedido y este haya completado todos los campos necesarios del formulario, se llama al metodo updateAddress del CartContext y esta se encarga de actualizar los datos en el contexto y posteriormente en las cookies, para luego, insertar estos datos en la orden generada.

    ⬆️ Volver al indice

    Pagina de resumen

    En esta pagina, se le mostrar al usuario todo lo relacionado a su orden y tambien podra confirmarla aqui mismo.
    Para ello, se utiliza el CartContext para tomar todos los datos de la orden a generar, que previamente se han establecido en las paginas anteriores. Se tomara el numero de items del carrito y el metodo createOrder que se llamara cuando el usuario de en Confirmar orden.

    const { numberOfItems, createOrder } = useContext(CartContext);


    El metodo createOrder, toma todos los datos de la orden y se los envia al backend. Cuando el backend retorne que fue creado exitosamente. Se enviara al usuario a pagina de la orden generada, donde debera realizar el pago.

    ⬆️ Volver al indice

    Pagina de orden

    En esta pagina, que es unica para cada orden generada, se le muestra al usuario si la orden fue paga o no, y si no lo ha sido, podra pagar su orden mediante PayPal.

    En esta pagina, se obtiene el id ingresado por parametros en la url y se la envia a un CustomHook (useGetOrder), que es encargado de realizar la peticion http al backend y obtener los datos de la orden si esta existe.

    const { id } = router.query as { id: string };
    const { order } = useGetOrder(id, `/api/orders/${id}`, `/orders/${id}`);

    Si la orden existe, se le muestra al usuario. Si no existe, se envia al usuario a la pagina de historial de ordenes. Y si existe pero el usuario no esta autenticado, se envia al usuario a la pagina de login.

    ⬆️ Volver al indice

    Pago de la orden

    Para realizar los pagos, se utiliza la libreria (@paypal/react-paypal-js), propia de PayPal. Esta te ofrece todo lo que se necesita para realizar los pagos con PayPal, tanto la logica como la UI de la misma.

    Para hacer uso de esta, debemos importar los botones de la libreria.

    import { PayPalButtons } from "@paypal/react-paypal-js";

    Luego, se hacen uso de este componente, pasandole un callback a las propiedades createOrder y onApprove. La primera se ejecuta cuando el usuario haga click en el boton, y tras esto, se le envia el monto a pagar a PayPal. La segunda, se ejecuta cuando la transaccion haya terminado, sin importar…