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

Obteniendo el token de la API

13/31
Recursos

Aportes 22

Preguntas 3

Ordenar por:

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

o inicia sesi贸n.

Para los errores utilic茅 un estado 鈥渆rrorLogin鈥 para mostrar el error y otro estado 鈥渓oading鈥 para mostrar un icono en el bot贸n y desahabilitar al enviar el form

const [errorLogin, setErrorLogin] = useState(null);
const [loading, setLoading] = useState(false);

En el submit un catch y seteo el error y el estado de loading para mostrar un icono

  const submitHandler = (event) => {
    event.preventDefault();
    const email = emailRef.current.value;
    const password = passwordRef.current.value;
    setErrorLogin(null);
    setLoading(true);
    auth
      .signIn(email, password)
      .then(() => {
        route.push('/dashboard');
      })
      .catch(function (error) {
        if (error.response?.status === 401) {
          setErrorLogin('Usuario o password incorrecto.');
        } else if (error.request) {
          setErrorLogin('Tenemos un problema');
        } else {
          setErrorLogin('Algo salimal.');
        }
        setLoading(false);
      });
  };

El si hay un error muestro el div con el mensaje

{errorLogin && (
  <div class="p-3 mb-3 text-sm text-red-700 bg-red-100 rounded-lg dark:bg-red-200 dark:text-red-800" role="alert">
    <span class="font-medium">Error!</span> {errorLogin}
  </div>
)}

Si se env铆a el formulario muestro un icono dentro del button

{loading && (
  <span class="flex absolute h-4 w-4 top-0 right-0 -mt-1 -mr-1">
    <span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-indigo-300 opacity-75"></span>
    <span class="relative inline-flex rounded-full h-4 w-4 bg-indigo-400"></span>
  </span>
)}

Y establezco disabled el button
disabled={loading}

para los errores cree dos constantes utilizando useState en el useAuth.js

const [error, setError]=useState();

  return {
    user,
    error,
    setError,
    signIn,
  };

En el login page, en el auth.signIn

    auth.signIn(email, password).then(
      () => {
        console.log('Login success');
      },
      (reason) => {
        console.log('Login Failed');
        console.error(reason);
        auth.setError('Invalid Username or Password');
      }
    );

y antes de cerrar el form para mostrar el estado del error agrege

            {auth.error ? (
              <div className="p-4 mb-4 text-sm text-red-700 bg-red-100 rounded-lg dark:bg-red-200 dark:text-red-800" role="alert">
                <span className="font-medium">Login Failed!</span> {auth.error}
              </div>
            ) : null}

Clase #13: Obteniendo el token de la API 13/31 馃攼

驴Qu茅 son las cookies? 馃崻

Las cookies son peque帽os fragmentos de texto que los sitios web que visitas env铆an al navegador. Permiten que los sitios web recuerden informaci贸n sobre tu visita, lo que puede hacer que sea m谩s f谩cil volver a visitar los sitios y hacer que estos te resulten m谩s 煤tiles. Fuente: aqu铆

Continuando con el proyecto: 馃敤


En VSC en la ruta src/hooks en el archivo useAuth.js quitamos el console.log de signIn y agregamos la estructura de la cookie:

if(access_token){
	Cookie.set('token', access_token.access_token, {expires: 5}); 
	//expires permite que despu茅s de un tiempo definido podamos eliminar la informaci贸n almacenada y pueda volver a logear
}

Una vez guardado, vamos a la consola, lo corremos con npm run dev, al ir al inspector de web, en Application sale una cuadro donde muestra el token generado, 茅sto quiere decir que la cookie guard贸 el token.

驴Qu茅 son las Cookies 馃崻?

Las Cookies es un archivo con datos creado y guardado en el ordenador de un usuario cuando visita paginas web que usan Cookies.

Este peque帽o archivo contiene cierta informaci贸n de usuario y/o otros tipo para su posterior reh煤so del mismo.

驴Porque se llaman Cookies 馃崻?

El nombre tan peculiar proviene del cl谩sico cuento de hadas 鈥淗ansel y Gretel鈥 en el cuento los hermanos dejan rastros de dulces y bas谩ndose en esta analog铆a se refiere al mismo rastros que dejamos con las visitas a los sitios web que visitamos y dejan en el ordenador estos archivos.

驴Para que sirven las Cookies 馃崻?

Las Cookies almacenan distintos tipos de informaci贸n de los usuarios con el objetivo de; identificarlo, recordar ciertas preferencias, ofrecerles contenido, etc.

Utilice el modal que estaba ya en el proyecto c: quedo as铆

== Obteniendo el token de la API ==

  1. Se hace la validacion si tenemos el access_token:
  2. Agregamos la informacion obtenida a una cookie con Cookies.set(), en el cual le pasaremos 3 parametros, el primero sera el nombre (string), el segundo sera el valor (access_token) , y el tercero sera la duracion ({expire}).
  • useAuth.js
function useProviderAuth() {
    const [user, setUser] = useState(null);

    const signIn = async (email, password) => {
        const options = {
            headers: {
                accept: '*/*',
                'Content-Type': 'application/json',
            },
        };
        //==Lectura del AccessToken que viene desde la api, para posteriormente agregarla a una cookie
        const { data: access_token } = await axios.post(endPoints.auth.login, { email, password }, options);
        if (access_token) {
            Cookies.set('token', access_token.access_token, { expires: 5 })
        }
    };
  1. Se agrego la logica de error en el login, se creo un estado error en useAuth.js:
 const [error , setError] = useState(false);
  1. Luego se agrego la logica en en handleSubmit del login page:
  const handleSubmit = (event) => {
    event.preventDefault();
    const user = userRef.current.value;
    const password = passwordRef.current.value;
    auth.signIn(user, password).then(() => {
      auth.setError(false);
      console.log("LoginSucces")
    },
      (err) => {
        console.log("Error Login");
        console.error(err);
        auth.setError(true);
      }
    );
  };
  1. Luego se agrego un div al final del formulario con la logica al obtener el error:
 {auth.error ? (
              <div className="p-4 mb-4 text-sm text-red-700 bg-red-100 rounded-lg dark:bg-red-200 dark:text-red-800" role="alert">
                <span className="font-medium">Login Failed!</span> {auth.error}
              </div>
            ) : null}

Resultado

Para la retroalimentaci贸n de errores use los toast de tailwind y el hook de useState

En loginPage

import { useRef, useState } from 'react';
import { LockClosedIcon } from '@heroicons/react/solid';
// import custom hook
import { useAuth } from '@hooks/useAuth';
// import components
import Toast from '@components/Toast';
import Spinner from '@components/Spinner';

export default function LoginPage() {
  const emailRef = useRef(null);
  const passwordRef = useRef(null);
  // In this case auth return the user and a function to sign in
  const auth = useAuth();

  // state of spinner
  const [loading, setLoading] = useState(false);
  // state of error
  const [error, setError] = useState(null);

  const submitHandler = (e) => {
    // prevent the form from submitting
    e.preventDefault();
    setLoading(true);
    setError(false);
    const email = emailRef.current.value;
    const password = passwordRef.current.value;

    // do something with the data, in this case the user signIn
    auth
      .signIn(email, password)
      .then(() => {
        console.log('signed in');
        setLoading(false);
      })
      .catch((err) => {
        setError(true);
        setLoading(false);
        console.log('Error signing: ', err);
      });
  };

  return (
    <>
      <div className="min-h-full flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
        <div className="max-w-md w-full space-y-8">
          <div>
            <img className="mx-auto h-12 w-auto" src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg" alt="Workflow" />
            <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Sign in to your account</h2>
          </div>
          <form className="mt-8 space-y-6" onSubmit={submitHandler}>
            <input type="hidden" name="remember" defaultValue="true" />
            <div className="rounded-md shadow-sm -space-y-px">
              <div>
                <label htmlFor="email-address" className="sr-only">
                  Email address
                </label>
                <input
                  id="email-address"
                  name="email"
                  type="email"
                  autoComplete="email"
                  required
                  className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                  placeholder="Email address"
                  ref={emailRef}
                />
              </div>
              <div>
                <label htmlFor="password" className="sr-only">
                  Password
                </label>
                <input
                  id="password"
                  name="password"
                  type="password"
                  autoComplete="current-password"
                  required
                  className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                  placeholder="Password"
                  ref={passwordRef}
                />
              </div>
            </div>

            <div className="flex items-center justify-between">
              <div className="flex items-center">
                <input id="remember-me" name="remember-me" type="checkbox" className="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" />
                <label htmlFor="remember-me" className="ml-2 block text-sm text-gray-900">
                  Remember me
                </label>
              </div>

              <div className="text-sm">
                <a href="/reset" className="font-medium text-indigo-600 hover:text-indigo-500">
                  Forgot your password?
                </a>
              </div>
            </div>

            <div>
              <button
                type="submit"
                className="group relative w-full flex justify-center py-2 px-4 border border-transparent 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"
              >
                <span className="absolute left-0 inset-y-0 flex items-center pl-3">
                  <LockClosedIcon className="h-5 w-5 text-indigo-500 group-hover:text-indigo-400" aria-hidden="true" />
                </span>
                Sign in
              </button>
            </div>
          </form>
          {loading && <Spinner />}
          {error && <Toast type="error" title="Error" message="Your email or password is incorrect, try again" time="now" />}
        </div>
      </div>
    </>
  );
}

El toast

import React from 'react';

export default function Toast({ type, title, message, time }) {
  return (
    <div className="flex flex-col justify-center">
      {/* info toast */}
      {type === 'info' && (
        <div
          className="bg-blue-600 shadow-lg mx-auto w-96 max-w-full text-sm pointer-events-auto bg-clip-padding rounded-lg block mb-3"
          id="static-example"
          role="alert"
          aria-live="assertive"
          aria-atomic="true"
          data-mdb-autohide="false"
        >
          <div className="bg-blue-600 flex justify-between items-center py-2 px-3 bg-clip-padding border-b border-blue-500 rounded-t-lg">
            <p className="font-bold text-white flex items-center">
              <svg
                aria-hidden="true"
                focusable="false"
                data-prefix="fas"
                data-icon="info-circle"
                className="w-4 h-4 mr-2 fill-current"
                role="img"
                
                viewBox="0 0 512 512"
              >
                <path
                  fill="currentColor"
                  d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"
                ></path>
              </svg>
              MDBootstrap
            </p>
            <div className="flex items-center">
              <p className="text-white opacity-90 text-xs">11 mins ago</p>
              <button
                type="button"
                className="btn-close btn-close-white box-content w-4 h-4 ml-2 text-white border-none rounded-none opacity-50 focus:shadow-none focus:outline-none focus:opacity-100 hover:text-white hover:opacity-75 hover:no-underline"
                data-mdb-dismiss="toast"
                aria-label="Close"
              ></button>
            </div>
          </div>
          <div className="p-3 bg-blue-600 rounded-b-lg break-words text-white">Hello, world! This is a toast message.</div>
        </div>
      )}

      {/* success toast */}
      {type === 'success' && (
        <div
          className="bg-green-500 shadow-lg mx-auto w-96 max-w-full text-sm pointer-events-auto bg-clip-padding rounded-lg block mb-3"
          id="static-example"
          role="alert"
          aria-live="assertive"
          aria-atomic="true"
          data-mdb-autohide="false"
        >
          <div className="bg-green-500 flex justify-between items-center py-2 px-3 bg-clip-padding border-b border-green-400 rounded-t-lg">
            <p className="font-bold text-white flex items-center">
              <svg
                aria-hidden="true"
                focusable="false"
                data-prefix="fas"
                data-icon="check-circle"
                className="w-4 h-4 mr-2 fill-current"
                role="img"
                
                viewBox="0 0 512 512"
              >
                <path
                  fill="currentColor"
                  d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"
                ></path>
              </svg>
              MDBootstrap
            </p>
            <div className="flex items-center">
              <p className="text-white opacity-90 text-xs">11 mins ago</p>
              <button
                type="button"
                className="btn-close btn-close-white box-content w-4 h-4 ml-2 text-white border-none rounded-none opacity-50 focus:shadow-none focus:outline-none focus:opacity-100 hover:text-white hover:opacity-75 hover:no-underline"
                data-mdb-dismiss="toast"
                aria-label="Close"
              ></button>
            </div>
          </div>
          <div className="p-3 bg-green-500 rounded-b-lg break-words text-white">Hello, world! This is a toast message.</div>
        </div>
      )}
      {/* danger toast */}

      {type === 'danger' && (
        <div
          className="bg-yellow-500 shadow-lg mx-auto w-96 max-w-full text-sm pointer-events-auto bg-clip-padding rounded-lg block mb-3"
          id="static-example"
          role="alert"
          aria-live="assertive"
          aria-atomic="true"
          data-mdb-autohide="false"
        >
          <div className="bg-yellow-500 flex justify-between items-center py-2 px-3 bg-clip-padding border-b border-yellow-400 rounded-t-lg">
            <p className="font-bold text-white flex items-center">
              <svg
                aria-hidden="true"
                focusable="false"
                data-prefix="fas"
                data-icon="exclamation-triangle"
                className="w-4 h-4 mr-2 fill-current"
                role="img"
               
                viewBox="0 0 576 512"
              >
                <path
                  fill="currentColor"
                  d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"
                ></path>
              </svg>
              MDBootstrap
            </p>
            <div className="flex items-center">
              <p className="text-white opacity-90 text-xs">11 mins ago</p>
              <button
                type="button"
                className="btn-close btn-close-white box-content w-4 h-4 ml-2 text-white border-none rounded-none opacity-50 focus:shadow-none focus:outline-none focus:opacity-100 hover:text-white hover:opacity-75 hover:no-underline"
                data-mdb-dismiss="toast"
                aria-label="Close"
              ></button>
            </div>
          </div>
          <div className="p-3 bg-yellow-500 rounded-b-lg break-words text-white">Hello, world! This is a toast message.</div>
        </div>
      )}
      {/* error toast */}
      {type === 'error' && (
        <div
          className="bg-red-600 shadow-lg mx-auto w-96 max-w-full text-sm pointer-events-auto bg-clip-padding rounded-lg block mb-3"
          id="static-example"
          role="alert"
          aria-live="assertive"
          aria-atomic="true"
          data-mdb-autohide="false"
        >
          <div className="bg-red-600 flex justify-between items-center py-2 px-3 bg-clip-padding border-b border-red-500 rounded-t-lg">
            <p className="font-bold text-white flex items-center">
              <svg
                aria-hidden="true"
                focusable="false"
                data-prefix="fas"
                data-icon="times-circle"
                className="w-4 h-4 mr-2 fill-current"
                role="img"
               
                viewBox="0 0 512 512"
              >
                <path
                  fill="currentColor"
                  d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z"
                ></path>
              </svg>
              {title != null ? title : 'MDBootstrap'}
            </p>
            <div className="flex items-center">
              <p className="text-white opacity-90 text-xs">{time != null ? time : '11 mins ago'}</p>
              <button
                type="button"
                className="btn-close btn-close-white box-content w-4 h-4 ml-2 text-white border-none rounded-none opacity-50 focus:shadow-none focus:outline-none focus:opacity-100 hover:text-white hover:opacity-75 hover:no-underline"
                data-mdb-dismiss="toast"
                aria-label="Close"
              ></button>
            </div>
          </div>
          <div className="p-3 bg-red-600 rounded-b-lg break-words text-white">{message != null ? message : 'Hello, world! This is a toast message.'}</div>
        </div>
      )}
    </div>
  );
}

Y el spinner

const Spinner = () => {
  return (
    <div class="flex justify-center items-center">
      <div class="spinner-border animate-spin inline-block w-8 h-8 border-4 rounded-full" role="status">
        <span class="visually-hidden">.</span>
      </div>
    </div>
  );
};

export default Spinner;

para el reto, lo que hice fue crear un estado local en el componente LoginPage

const [loginError, setLoginError] = useState(false);

modifique la funci贸n handleSubmit que se dispara al intentar loguearnos, cuando el usuario y contrase帽a son correctos el valor de este estado local ser谩 seteado a true, caso contrario, ser谩 seteado a false

const handleSubmit = (e)=> {
  e.preventDefault();
  const email = emailRef.current.value;
  const password = passwordRef.current.value;
  
  auth.signIn(email,password)
    .then(res => {
      setLoginError(false);
      console.log('login success');
    })
    .catch(err => {
      setLoginError(true);
      console.log('login error');
    })
}

por ultimo, hice una validaci贸n con un ternario en el render de este componente LoginPage

export default function LoginPage() {
  const [loginError, setLoginError] = useState(false);

  ....
   ..

  return (
    <>
      ....
      {loginError 
                ? <p className='text-center'>Invalid username or password</p>
                : ''
              }
      ....
    </>
  );
}

el c贸digo completo quedar铆a de esta forma
src/components/LoginPage.jsx:

import { useRef, useState } from 'react';
import { LockClosedIcon } from '@heroicons/react/24/solid';
import { useAuth } from '@hooks/useAuth';

export default function LoginPage() {
  const [loginError, setLoginError] = useState(false);

  const emailRef = useRef(null)
  const passwordRef = useRef(null)
  const auth = useAuth();

  const handleSubmit = (e)=> {
    e.preventDefault();
    const email = emailRef.current.value;
    const password = passwordRef.current.value;
    
    auth.signIn(email,password)
      .then(res => {
        setLoginError(false);
        console.log('login success');
      })
      .catch(err => {
        setLoginError(true);
        console.log('login error');
      })
  }

  return (
    <>
      <div className="min-h-full flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
        <div className="max-w-md w-full space-y-8">
          <div>
            <img className="mx-auto h-12 w-auto" src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg" alt="Workflow" />
            <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Sign in to your account</h2>
          </div>
            <form className="mt-8 space-y-6" onSubmit={handleSubmit} >
              <input type="hidden" name="remember" defaultValue="true" />
              <div className="rounded-md shadow-sm -space-y-px">
                <div>
                  <label htmlFor="email-address" className="sr-only">
                    Email address
                  </label>
                  <input
                    id="email-address"
                    name="email"
                    type="email"
                    autoComplete="email"
                    required
                    className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                    placeholder="Email address"
                    ref={emailRef}
                  />
                </div>
                <div>
                  <label htmlFor="password" className="sr-only">
                    Password
                  </label>
                  <input
                    id="password"
                    name="password"
                    type="password"
                    autoComplete="current-password"
                    required
                    className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                    placeholder="Password"
                    ref={passwordRef}
                  />
                </div>
              </div>

              <div className="flex items-center justify-between">
                <div className="flex items-center">
                  <input id="remember-me" name="remember-me" type="checkbox" className="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" />
                  <label htmlFor="remember-me" className="ml-2 block text-sm text-gray-900">
                    Remember me
                  </label>
                </div>

                <div className="text-sm">
                  <a href="#" className="font-medium text-indigo-600 hover:text-indigo-500">
                    Forgot your password?
                  </a>
                </div>
              </div>

              <div>
                <button
                  type="submit"
                  className="group relative w-full flex justify-center py-2 px-4 border border-transparent 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"
                >
                  <span className="absolute left-0 inset-y-0 flex items-center pl-3">
                    <LockClosedIcon className="h-5 w-5 text-indigo-500 group-hover:text-indigo-400" aria-hidden="true" />
                  </span>
                  Sign in
                </button>
              </div>
              {loginError 
                ? <p className='text-center'>Invalid username or password</p>
                : ''
              }
            </form>
        </div>
      </div>
    </>
  );
}

Mientras estemos trabajando con el login, me parece util tener quedamos los valores que iran en email y password para evitar estar escribiendolo repetidas veces. 馃槂

const [value,setValue] = useState({email:'admin@mail.com',password:'admin123'})

As铆 que cree ese estado, y se los pase a los inputs.

Obviamente es algo que se debe de borrar antes de subir a producci贸n

Para el manejo de errores, agregu茅 un estado:

const [error, setError] = useState(null);

Y una funci贸n para modificar el estado:

const loginError = (message) =>{
	setError(message)
}

Tanto el estado como la funci贸n los agregu茅 al return:

return {
	user,
        signIn,
        error,
        loginError
};

En la promesa de singIn, agregu茅 el bloque catch:

 auth.signIn(email, password)
    .then(() =>{
      console.log('Login success');
    })
    .catch(() =>{
      auth.loginError('Wrong credentials!')
    });

Y finalmente, antes de la etiqueta de cierre </form>, agregu茅:

{
	auth.error && <p>{auth.error}</p>
}

Buenas, dar茅 una alternativa en caso de que quieran hacer una alerta sencilla, les recomiendo usar 鈥渞eact-hot-toast鈥, es muy sencilla y es compatible con tailwind, aqui les dejo su web:

https://react-hot-toast.com/docs

[ RETO ]
instal茅 la librer铆a Sweet Alert

link: https://sweetalert2.github.io/#download
npm install sweetalert2

Import茅 la librer铆a

import Swal from 'sweetalert2鈥
agregu茅 el m茅todo catch

Cuando se meten credenciales incorrectas muestra el error

Mi soluci贸n al reto de la clase:

import { useRef, useState } from 'react';
import { LockClosedIcon } from '@heroicons/react/24/solid';
import { useAuth } from '@hooks/useAuth';

export default function LoginPage() {
  const emailRef = useRef(null);
  const passwordRef = useRef(null);
  const auth = useAuth(null);
  const [loginStatus, setLoginStatus] = useState(null);
  const submitHandler = (event) => {
    event.preventDefault();
    const email = emailRef.current.value;
    const password = passwordRef.current.value;
    auth
      .signIn(email, password)
      .then((response) => {
        console.log('login successful');
        setLoginStatus(201);
      })
      .catch((err) => {
        if (err.response?.status === 401) {
          setLoginStatus(err.response?.status);
        }
      });
  };
  return (
    <>
      <div className="min-h-full flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
        <div className="max-w-md w-full space-y-8">
          <div>
            <img className="mx-auto h-12 w-auto" src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg" alt="Workflow" />
            <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">Sign in to your account</h2>
          </div>
          <form className="mt-8 space-y-6" onSubmit={submitHandler}>
            <input type="hidden" name="remember" defaultValue="true" />
            <div className="rounded-md shadow-sm -space-y-px">
              <div>
                <label htmlFor="email-address" className="sr-only">
                  Email address
                </label>
                <input
                  id="email-address"
                  name="email"
                  type="email"
                  autoComplete="email"
                  required
                  className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                  placeholder="Email address"
                  ref={emailRef}
                />
              </div>
              <div>
                <label htmlFor="password" className="sr-only">
                  Password
                </label>
                <input
                  id="password"
                  name="password"
                  type="password"
                  autoComplete="current-password"
                  required
                  className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                  placeholder="Password"
                  ref={passwordRef}
                />
              </div>
            </div>

            {loginStatus === 401 && (
              <div class="p-3 mb-3 text-sm text-red-700 bg-red-100 rounded-lg dark:bg-red-200 dark:text-red-800" role="alert">
                <span class="font-medium">Incorrect email or password</span>
              </div>
            )}

            <div className="flex items-center justify-between">
              <div className="flex items-center">
                <input id="remember-me" name="remember-me" type="checkbox" className="h-4 w-4 text-indigo-600 focus:ring-indigo-500 border-gray-300 rounded" />
                <label htmlFor="remember-me" className="ml-2 block text-sm text-gray-900">
                  Remember me
                </label>
              </div>

              <div className="text-sm">
                <a href="/forgot" className="font-medium text-indigo-600 hover:text-indigo-500">
                  Forgot your password?
                </a>
              </div>
            </div>

            <div>
              <button
                type="submit"
                className="group relative w-full flex justify-center py-2 px-4 border border-transparent 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"
              >
                <span className="absolute left-0 inset-y-0 flex items-center pl-3">
                  <LockClosedIcon className="h-5 w-5 text-indigo-500 group-hover:text-indigo-400" aria-hidden="true" />
                </span>
                Sign in
              </button>
            </div>
          </form>
        </div>
      </div>
    </>
  );
}

Primero pens茅 en un modal pero me pareci贸 algo intrusivo para un login fail as铆 que lo mantuve simple desplegando texto abajo si falla

a帽ad铆 unos estados:

const [error, setError] = useState(false)
const [loading, setLoading] = useState(false)
const [errorMessage, setErrorMessage] = useState('')

Adapt茅 la funci贸n de env铆o:

const handleSubmit = (event) => {
    event.preventDefault()
    setError(false)
    setLoading(true)
    const email = emailRef.current.value
    const password = passwordRef.current.value

    auth.signIn(email, password).then(() => {
      setLoading(false)
      console.log('Login success...')
    })
    .catch((err) => {
      setLoading(false)
      setError(true)
      const { response } = err
      setErrorMessage(response.statusText)
    })
  }

Y a帽ad铆 estados al bot贸n as铆 como el div y el dise帽o de los input:

<div>
                <label htmlFor="email-address" className="sr-only">
                  Email address
                </label>
                <input
                  id="email-address"
                  name="email"
                  type="email"
                  autoComplete="email"
                  required
                  className={!!!error
                    ?
                      "appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                    :
                      "appearance-none rounded-none relative block w-full px-3 py-2 border border-red-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-red-500 focus:border-red-500 focus:z-10 sm:text-sm"
                  }
                  placeholder="Email address"
                  ref={emailRef}
                  />
              </div>
              <div>
                <label htmlFor="password" className="sr-only">
                  Password
                </label>
                <input
                  id="password"
                  name="password"
                  type="password"
                  autoComplete="current-password"
                  required
                  className={!!!error
                    ?
                      "appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
                    :
                      "appearance-none rounded-none relative block w-full px-3 py-2 border border-red-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-red-500 focus:border-red-500 focus:z-10 sm:text-sm"
                  }
                  placeholder="Password"
                  ref={passwordRef}
                />
              </div>
<button
                type="submit"
                className="group relative w-full flex justify-center py-2 px-4 border border-transparent 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"
                >
                <span className="absolute left-0 inset-y-0 flex items-center pl-3">
                  {!!loading 
                    ? 
                      <ArrowPathIcon
                        className='h-5 w-5 text-indigo-400 animate-spin ease-in-out'
                        aria-hidden="true" 
                      />
                    : !! error ?
                      <ExclamationTriangleIcon
                        className="h-5 w-5 text-indigo-400"
                        aria-hidden="true"
                      />
                    :
                      <LockClosedIcon
                        className="h-5 w-5 text-indigo-500 group-hover:text-indigo-400"
                        aria-hidden="true"
                      />
                  }
                </span>
                Sign in
              </button>
const [loginError, setLoginError] = useState(false);

.signIn(email, password)
      .then(() => {
        console.log('login success');
      })
      .catch((error) => {
        console.log(error.message);
        setLoginError(true);
        setTimeout(() => {
          setLoginError(false);
        }, 2000);
      });
{loginError && (
              <div className="font-medium text-red-600 hover:text-red-500">
                <span>loginError</span>
              </div>
            )}

Solucion Login Request failed

        await axios.post(endPoints.auth.login, { email, password }, options)
            .then(value => Cookies.set('token', value.data.access_token, {expires: 5}))
            .catch(error => {
                console.error(error)
                // show a errorModal
            });

Mi soluci贸n para mostrar un mensaje de error o exito al usuario

useAuth.js

const signIn = async (email, password) => { 
    const options = {
      headers: {
        accept: '*/*',
        'Content-Type': 'application/json',
      },
    };
    const { data } = await axios.post(endPoints.auth.login, { email, password }, options);
    
    if (data.access_token) {
      Cookie.set('token', data.access_token, {expires: 5});
    }
    return data;
  }

LoginPage.js

const handleSubmit = (e) => {
  e.preventDefault();
  const email = emailRef.current.value;
  const password = passwordRef.current.value;
  
  auth.signIn(email, password).then((response) => {
    if (response.access_token) {
      setLoginSuccess(200);
    }
  }).catch((error) => {
    setLoginSuccess(error.response.status);
  });
};

{loginSuccess === 401 && (
  <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">
    <strong className="block sm:inline">Email or password is incorrect.</strong>
  </div>
)}

{loginSuccess === 200 && (
  <div className="bg-green-100 border border-green-400 text-green-700 px-4 py-3 rounded relative" role="alert">
    <strong className="block sm:inline">Login success.</strong>
  </div>
)}	

Lo hice con SweetAlert2 y algo del router de next.js

Ac谩 los imports

import { useRouter } from 'next/router';
import { useSate, useRef } from 'react';
import Swal from 'sweetalert2';

import { useAuth } from '@hooks/useAuth';

import { LockClosedIcon } from '@heroicons/react/solid';

Y ac谩 lo anterior al return

  const passwordRef = useRef(null);
  const emailRef = useRef(null);
  const router = useRouter();
  const auth = useAuth();

  const submitHandler = (event) => {
    event.preventDefault();
    const password = passwordRef.current.value;
    const email = emailRef.current.value;

    auth
      .singIn(email, password)
      .then(() => {
        router.push('/dashboard');
        Swal.fire({
          title: 'Welcome',
          icon: 'success',
          confirmButtonText: 'Okay',
        });
      })
      .catch((error) => {
        if (error.response?.status === 401) {
          Swal.mixin({
            toast: true,
            title: 'Error!',
            text: `This user doesn't exist, it migth be an error on your user or password`,
            icon: 'error',
            confirmButtonText: 'Okay',
          });
        } else if (error.resquest) {
          Swal.mixin({
            toast: true,
            title: 'Ups...',
            text: 'Something went wrong, we are experimenting some toubles, try it later',
            icon: 'error',
            confirmButtonText: 'Okay',
          });
        }
      });
  };

Por si a alguien le interesa, para instarlar SweetAlert, usas

npm install sweetalert2

Utiliz茅 el componente modal que nos ofrece tailwind UI y lo adapt茅 un poco

Componente LoginError

A帽ad铆 un catch en LoginPage.jsx

si quieren ver su cookie actual en cualquier navegador ctrl+shit+c, ejecutar un
alert(document.cookie)