Tela de login no CrazyStack Next.js

Dev Doido - Mar 8 '23 - - Dev Community

Nesta aula, vamos criar uma tela de login utilizando o método de design atomic e a biblioteca Chakra UI. Começaremos revisando o que é o design atomic e sua importância para a construção de interfaces de usuário consistentes e de fácil manutenção. Em seguida, vamos criar os componentes atômicos necessários para a nossa tela de login, como o input, o botão e o formulário.

Image description

O vídeo dessa aula está publicada no bootcamp CrazyStack, se você ainda não garantiu sua vaga clique aqui

Para criar esses componentes, utilizaremos as propriedades e componentes oferecidos pela Chakra UI, que é uma biblioteca de componentes UI React construída com base no design system da figma. A Chakra UI oferece uma ampla gama de componentes prontos para uso, além de fornecer opções de personalização para que você possa adaptar a aparência dos componentes às suas necessidades.

Depois de criarmos os componentes atômicos, vamos utilizá-los para montar a nossa tela de login. Nesta etapa, vamos prestar atenção à consistência da aparência e interação dos componentes, além de garantir que eles sejam fáceis de usar e entender para o usuário. Além disso, vamos adicionar a funcionalidade de autenticação à nossa tela de login, utilizando a hook useState do React para gerenciar o estado do formulário.

Por fim, vamos testar nossa tela de login para garantir que tudo esteja funcionando corretamente. Para isso, utilizaremos a biblioteca de teste Jest, que nos permitirá escrever testes unitários para verificar se os componentes estão renderizando corretamente, se estão reagindo a interações do usuário da maneira esperada e se a funcionalidade de autenticação está funcionando corretamente.

Em resumo, nesta aula, você aprenderá como criar uma tela de login utilizando o design atomic e a Chakra UI, e como testá-la para garantir que ela esteja funcionando corretamente. Ao seguir este processo, você estará adquirindo habilidades valiosas para construir interfaces de usuário consistentes e de fácil manutenção, além de garantir a qualidade do seu código.



import { Input as InputChakra, InputProps as ChakraInputProps } from "@chakra-ui/react";
import { ForwardRefRenderFunction, forwardRef } from "react";
interface InputProps extends ChakraInputProps {
  name: string;
}
const InputAtom: ForwardRefRenderFunction<HTMLInputElement, InputProps> = (
  { name, ...rest },
  ref
) => {
  return (
    <InputChakra {...rest} data-testid="InputTestId" id={name} name={name} ref={ref} />
  );
};
export const Input = forwardRef(InputAtom); 


Enter fullscreen mode Exit fullscreen mode

Este código é uma implementação de um componente de Input utilizando a biblioteca Chakra UI e o React. O componente é escrito em TypeScript.

A primeira linha importa o componente Input e as propriedades desse componente da biblioteca Chakra UI. A segunda linha importa as funções ForwardRefRenderFunction e forwardRef do React.

Em seguida, é criada uma interface InputProps que extende as propriedades padrão do componente Input da Chakra UI e adiciona a propriedade name. Essa propriedade será utilizada para identificar o input de forma única no formulário.

Em seguida, é definido o componente InputAtom como uma função que utiliza ForwardRefRenderFunction e forwardRef. Essa função tem como objetivo renderizar o componente Input da Chakra UI. O componente recebe duas props, { name, ...rest } e ref, que serão utilizadas para preencher as propriedades do componente.

Dentro da função InputAtom, é retornado o componente InputChakra da Chakra UI, que é utilizado para renderizar o input. Além das propriedades passadas via rest, são adicionadas as propriedades data-testid, id e name, que correspondem ao atributo data-testid para testes automatizados, o identificador único do input e o nome do input, respectivamente. A propriedade ref é utilizada para fornecer acesso direto ao elemento DOM do input.

Por fim, é exportado o componente Input que é criado a partir da função forwardRef que recebe a função InputAtom como argumento. Esse componente será utilizado em outros lugares da aplicação para renderizar o input personalizado.

Em resumo, este código cria um componente de input personalizado baseado no componente Input da biblioteca Chakra UI e o torna mais fácil de ser utilizado em outras partes da aplicação, fornecendo acesso direto ao elemento DOM e permitindo que sejam definidas propriedades adicionais.



import { renderWithTheme } from "test/testUtils";
import { screen } from "@testing-library/react";
import { Input } from "./Input";

describe("<Input/>", () => {
  it("should render the Input component", () => {
    renderWithTheme(<Input name="test" />);
    expect(screen.getByTestId("InputTestId")).toBeInTheDocument();
    expect(screen.getByTestId("InputTestId")).toHaveAttribute("name", "test");
  });
});


Enter fullscreen mode Exit fullscreen mode

Esse código é um teste unitário escrito em Jest + Testing Library para o componente Input de uma aplicação React. O teste tem o objetivo de garantir que o componente esteja sendo renderizado corretamente, possuindo os atributos esperados, e de verificar se ele está reagindo a interações do usuário da maneira esperada.

A linha import { renderWithTheme } from "test/testUtils"; importa a função renderWithTheme que é responsável por renderizar o componente Input com o tema da aplicação.

A linha import { screen } from "@testing-library/react"; importa o objeto screen que contém métodos para buscar elementos na página renderizada.

A linha import { Input } from "./Input"; importa o componente Input que será testado.

O código está dentro de um bloco de descrição describe("<Input/>", () => { ... }); que define o escopo do teste. Nesse bloco, há apenas um teste, identificado pelo método it("should render the Input component", () => { ... });.

Dentro desse teste, a linha renderWithTheme(<Input name="test" />); renderiza o componente Input com o tema da aplicação, passando a propriedade name com o valor "test".

A linha expect(screen.getByTestId("InputTestId")).toBeInTheDocument(); verifica se o elemento com o atributo data-testid igual a "InputTestId" está presente na página.

E a linha expect(screen.getByTestId("InputTestId")).toHaveAttribute("name", "test"); verifica se o elemento com o atributo data-testid igual a "InputTestId" possui o atributo name com o valor "test".

Em resumo, esse código é um teste unitário que garante que o componente Input esteja sendo renderizado corretamente, possuindo os atributos esperados e que esteja reagindo a interações do usuário da maneira esperada.

Desafio: Criar mais testes unitários para esse Input

Possível exemplo:



it("should render the Input component with a label", () => {
  renderWithTheme(
    <Input name="test" label="Test Input" aria-label="Test Input" />
  );
  expect(screen.getByText("Test Input")).toBeInTheDocument();
  expect(screen.getByTestId("InputTestId")).toHaveAttribute("aria-label", "Test Input");
});

it("should render the Input component with a specific type", () => {
  renderWithTheme(<Input name="test" type="password" />);
  expect(screen.getByTestId("InputTestId")).toHaveAttribute("type", "password");
});

it("should render the Input component with a placeholder", () => {
  renderWithTheme(<Input name="test" placeholder="Enter text here" />);
  expect(screen.getByTestId("InputTestId")).toHaveAttribute(
    "placeholder",
    "Enter text here"
  );
});

it("should render the Input component with a default value", () => {
  renderWithTheme(<Input name="test" defaultValue="Default value" />);
  expect(screen.getByTestId("InputTestId")).toHaveAttribute(
    "value",
    "Default value"
  );
});

it("should render the Input component with a specific size", () => {
  renderWithTheme(<Input name="test" size="lg" />);
  expect(screen.getByTestId("InputTestId")).toHaveClass("chakra-input__lg");
});

it("should call the onChange function when the value changes", () => {
  const onChangeMock = jest.fn();
  renderWithTheme(<Input name="test" onChange={onChangeMock} />);
  fireEvent.change(screen.getByTestId("InputTestId"), {
    target: { value: "Changed value" },
  });
  expect(onChangeMock).toHaveBeenCalled();
});



Enter fullscreen mode Exit fullscreen mode


import { Checkbox as CheckboxChakra, CheckboxProps } from "@chakra-ui/react";
export const Checkbox = ({ children, ...rest }: CheckboxProps) => {
  return (
    <CheckboxChakra {...rest} data-testid="CheckboxTestId">
      {children}
    </CheckboxChakra>
  );
};


Enter fullscreen mode Exit fullscreen mode

Este código define um componente personalizado de checkbox usando a biblioteca Chakra UI. O componente é criado como uma função simples que retorna um componente CheckboxChakra da biblioteca Chakra UI.

A função aceita um objeto de propriedades (CheckboxProps) e passa as propriedades para o componente CheckboxChakra da Chakra UI usando o spread operator (...rest). Além disso, o componente contém um atributo data-testid="CheckboxTestId", que pode ser usado pelos testes automatizados para identificar o componente na página.

O componente personalizado também inclui um propriedade children, que pode ser usada para definir o conteúdo que será exibido dentro do checkbox. Esse conteúdo pode ser texto ou outro componente.

Em resumo, esse código cria um componente de checkbox personalizado que herda as propriedades e estilo da biblioteca Chakra UI, mas adiciona uma identificação específica para testes automatizados.



import { renderWithTheme } from "test/testUtils";
import { screen } from "@testing-library/react";
import { Checkbox } from "./Checkbox";

describe("<Checkbox/>", () => {
  it("should render the Checkbox component", () => {
    renderWithTheme(<Checkbox />);
    expect(screen.getByTestId("CheckboxTestId")).toBeInTheDocument();
  });
});


Enter fullscreen mode Exit fullscreen mode

Este é um exemplo de um teste unitário para o componente Checkbox escrito usando Jest e @testing-library/react.

  • A primeira linha importa a função renderWithTheme do arquivo test/testUtils. Essa função é usada para renderizar o componente Checkbox com o tema configurado.

  • A segunda linha importa a função screen do pacote @testing-library/react. screen é usado para acessar elementos na página renderizada.

  • A terceira linha importa o componente Checkbox de ./Checkbox.

  • Em seguida, temos o bloco describe com o título "<Checkbox/>". Isso define um grupo de testes para o componente Checkbox.

  • Dentro desse bloco describe, temos o teste it com o título "should render the Checkbox component". Este teste verifica se o componente Checkbox foi corretamente renderizado na página.

  • Dentro do teste, a função renderWithTheme é chamada com o componente Checkbox como argumento. Isso renderiza o componente na página.

  • Em seguida, a função expect é chamada com screen.getByTestId("CheckboxTestId") como argumento. Isso verifica se um elemento com o data-testid "CheckboxTestId" está presente na página.

  • Finalmente, a função toBeInTheDocument é chamada para verificar se o elemento está realmente no documento. Se o elemento estiver presente, o teste será considerado um sucesso. Caso contrário, o teste será considerado um fracasso.

    Desafio: Mais testes para Checkbox

    Aqui estão alguns exemplos adicionais de testes unitários que você pode escrever para o componente Checkbox:



it("should render the label correctly", () => {
  const label = "Checkbox Label";
  renderWithTheme(<Checkbox>{label}</Checkbox>);
  expect(screen.getByText(label)).toBeInTheDocument();
});

it("should have the correct checked state", () => {
  renderWithTheme(<Checkbox isChecked />);
  expect(screen.getByTestId("CheckboxTestId")).toBeChecked();
});

it("should call the onChange function when checked", () => {
  const onChange = jest.fn();
  renderWithTheme(<Checkbox onChange={onChange} />);
  fireEvent.click(screen.getByTestId("CheckboxTestId"));
  expect(onChange).toHaveBeenCalled();
});

it("should have the correct disabled state", () => {
  renderWithTheme(<Checkbox isDisabled />);
  expect(screen.getByTestId("CheckboxTestId")).toBeDisabled();
});



Enter fullscreen mode Exit fullscreen mode


import Router from "next/router";
import type { NextPage } from "next";

import { Head, Flex, Text } from "shared/ui";
export const Login: NextPage = () => {
  return (
    <>
      <Head
        title="Belezix Admin | Login"
        description="Página de login do painel Admin do Belezix"
      />
      <Flex minW="100%" justifyContent="center">
        <Flex mt="15%">
          <Text fontSize="6xl">CrazyStack ReactJs</Text>
        </Flex>
      </Flex>
    </>
  );
};


Enter fullscreen mode Exit fullscreen mode

Esse código define uma página de login, que será usada como a página de entrada do painel administrativo do aplicativo. Ele é escrito em Next.js, que é uma biblioteca de gerenciamento de rotas e renderização em servidor para aplicativos React.

A página Login importa os componentes Head, Flex, Text do diretório shared/ui. Esses componentes fornecem uma interface para o usuário, mas não são detalhados neste código.

A função Login retorna um componente React que contém dois componentes Flex. O primeiro é usado para centralizar o conteúdo na tela, o segundo para exibir o título "CrazyStack ReactJs". O componente Head é usado para definir o título da página e a descrição para o mecanismo de busca.



import axios from "axios";
import { parseCookies } from "nookies";
export function setupAPIClient(ctx = undefined) {
  const cookies = parseCookies(ctx);
  const api = axios.create({
    baseURL: "http://localhost:3333",
    headers: { Authorization: `Bearer ${cookies["belezixadmin.token"]}` },
  });
  return api;
}
export const api = setupAPIClient();


Enter fullscreen mode Exit fullscreen mode

Esse código é uma implementação de uma função setupAPIClient que cria um cliente de API do Axios. Ele também cria uma instância global api do cliente.

A função setupAPIClient recebe um opcional contexto ctx como parâmetro. O contexto ctx é comumente utilizado no Next.js para armazenar informações de contexto durante a execução de uma página. Ele utiliza a função parseCookies do pacote nookies para recuperar as cookies a partir do contexto e armazená-las na variável cookies.

A função setupAPIClient usa a função axios.create para criar uma instância do cliente Axios. A propriedade baseURL da instância é definida como "http://localhost:3333", o que significa que todas as solicitações feitas através dessa instância do Axios terão a URL base definida como "http://localhost:3333". A propriedade headers é definida como um objeto com uma chave Authorization que contém um token de autorização extraído da cookie cookies["belezixadmin.token"].

A instância api é criada chamando a função setupAPIClient sem passar qualquer contexto. Isso significa que a instância api terá as mesmas propriedades que uma instância criada por setupAPIClient chamada sem passar o contexto.

Auth Context

Caso queiramos implementar um contexto de autenticação, poderia ser algo assim:



import React, { createContext, useState, useEffect } from "react";
import axios from "axios";

interface AuthContextData {
  user: any;
  loading: boolean;
  signIn(email: string, password: string): Promise<void>;
  signOut(): void;
}

const AuthContext = createContext<AuthContextData>({} as AuthContextData);

export const AuthProvider: React.FC = ({ children }) => {
  const [user, setUser] = useState<any>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const token = localStorage.getItem("@belezix:token");
    if (token) {
      setUser(token);
      setLoading(false);
    } else {
      setLoading(false);
    }
  }, []);

  const signIn = async (email: string, password: string) => {
    const response = await axios.post("http://localhost:3333/sessions", {
      email,
      password,
    });

    const { token } = response.data;
    localStorage.setItem("@belezix:token", token);
    setUser(token);
  };

  const signOut = () => {
    localStorage.removeItem("@belezix:token");
    setUser(null);
  };

  return (
    <AuthContext.Provider value={{ user, loading, signIn, signOut }}>
      {children}
    </AuthContext.Provider>
  );
};

export function useAuth(): AuthContextData {
  const context = React.useContext(AuthContext);

  if (!context) {
    throw new Error("useAuth must be used within an AuthProvider");
  }

  return context;
}



Enter fullscreen mode Exit fullscreen mode

Neste exemplo, a função signIn envia uma requisição POST para o endpoint de autenticação passando o email e a senha. Se a autenticação for bem-sucedida, a resposta irá conter um token, que será armazenado no localStorage e definido como o usuário atual. A função signOut simplesmente remove o token do localStorage e define o usuário atual como null.
No entanto, no curso não usamos localStorage para armazenar informações de autenticação, e sim os cookies. Ficando assim:



import { createContext, useContext, ReactNode, useState, useMemo } from "react";
import { setCookie, destroyCookie } from "nookies";
import Router from "next/router";
import { api } from "shared/api";
type User = {
  email: string;
  role: string;
  _id: string;
};
type AuthProviderProps = {
  children: ReactNode;
};
type LoginCredentials = {
  email: string;
  password: string;
};
type AuthContextData = {
  login(credentials: LoginCredentials): Promise<void>;
  isAuthenticated: boolean;
  user: User | null;
};
const AuthContext = createContext({} as AuthContextData);

export function AuthProvider({ children }: AuthProviderProps) {
  const [user, setUser] = useState<User | null>(null);
  const isAuthenticated = !!user;
  const login = async ({ email, password }: LoginCredentials) => {
    try {
      const response = await api.post("auth/login", {
        email,
        password,
        passwordConfirmation: password,
      });
      const { accessToken: token, refreshToken, user: userComing } = response?.data || {};
      setCookie(undefined, "belezixadmin.token", token, {
        maxAge: 60 * 60 * 24 * 30,
        path: "/",
      });
      setCookie(undefined, "belezixadmin.refreshToken", refreshToken, {
        maxAge: 60 * 60 * 24 * 30,
        path: "/",
      });
      setCookie(undefined, "belezixadmin.user", JSON.stringify(userComing), {
        maxAge: 60 * 60 * 24 * 30,
        path: "/",
      });
      setUser(userComing);
      api.defaults.timeout = 5000;
      api.defaults.headers["Authorization"] = `Bearer ${token}`;
      Router.push("/");
    } catch (error) {
      alert("erro no servidor");
    }
  };
  const contextValue = useMemo(() => ({ login, isAuthenticated, user }), [user]);
  return <AuthContext.Provider value={contextValue}>{children}</AuthContext.Provider>;
}
export const useAuth = () => useContext(AuthContext);

export function signOut() {
  destroyCookie(undefined, "belezixadmin.token");
  destroyCookie(undefined, "belezixadmin.refreshToken");
  destroyCookie(undefined, "belezixadmin.user");
  Router.push("/");
}


Enter fullscreen mode Exit fullscreen mode

Este é um exemplo de código de um contexto de autenticação (AuthContext) utilizando o Context API do React.

Aqui, é criado um contexto de autenticação, que irá armazenar informações do usuário logado e prover funções para realizar o login. É criada uma função AuthProvider que irá funcionar como um "provedor" de contexto, ou seja, ele será o responsável por fornecer o contexto para seus componentes filhos.

Uma vez que o contexto é criado, as informações e funções que são compartilhadas em toda a aplicação são o estado do usuário logado (user), se o usuário está autenticado (isAuthenticated) e a função de login (login). A função de login é responsável por realizar a requisição para o endpoint de login, passando as credenciais do usuário (email e senha), e armazenando o token de acesso no cookie da aplicação.

Em vez de armazenar informações do usuário logado no localStorage, este código utiliza cookies para isso. A biblioteca nookies é utilizada para manipular os cookies. Quando o usuário faz login, as informações do usuário são armazenadas no cookie com nomes belezixadmin.token, belezixadmin.refreshToken e belezixadmin.user. Quando o usuário faz logout, as informações são removidas dos cookies utilizando a função destroyCookie.

Além disso, a função useAuth é fornecida como um hook para ser utilizada por outros componentes para obter informações do contexto de autenticação.

Porque usar cookies?

O uso de cookies ao invés do local storage para armazenar informações de autenticação tem algumas vantagens. Algumas delas incluem:

  1. Segurança: Cookies são enviados de volta para o servidor com cada requisição, o que significa que é mais difícil para um invasor interceptar a informação do usuário. Além disso, cookies podem ser configurados com atributos de segurança, como "HTTPOnly", para impedir que eles sejam acessados por scripts maliciosos.

  2. Facilidade de uso: Cookies são enviados automaticamente com cada requisição, o que significa que você não precisa se preocupar em enviar manualmente a informação do usuário com cada requisição.

  3. Armazenamento limitado: Cookies têm um tamanho limitado de 4 KB, o que os torna uma escolha adequada para armazenar informações simples, como tokens de autenticação.

Além disso, no contexto do Next.js, o uso de cookies é mais adequado do que o local storage, pois o local storage é um recurso do navegador que não está disponível em todas as áreas do aplicativo Next.js, enquanto o uso de cookies é possível em todas as áreas do aplicativo, incluindo a renderização do lado do servidor.

Como isso seria feito usando Redux?

O uso do Redux para armazenar o estado do contexto de autenticação seria algo assim:



import { createStore, combineReducers } from "redux";
import { applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { api } from "shared/api";

type User = {
  email: string;
  role: string;
  _id: string;
};

type LoginCredentials = {
  email: string;
  password: string;
};

const LOGIN = "LOGIN";
const LOGOUT = "LOGOUT";

type LoginAction = {
  type: typeof LOGIN;
  payload: User;
};

type LogoutAction = {
  type: typeof LOGOUT;
};

type Action = LoginAction | LogoutAction;

const login = (credentials: LoginCredentials) => async (dispatch: any) => {
  try {
    const response = await api.post("auth/login", {
      email: credentials.email,
      password: credentials.password,
      passwordConfirmation: credentials.password,
    });

    const { accessToken: token, refreshToken, user } = response?.data || {};

    localStorage.setItem("belezixadmin.token", token);
    localStorage.setItem("belezixadmin.refreshToken", refreshToken);
    localStorage.setItem("belezixadmin.user", JSON.stringify(user));

    dispatch({
      type: LOGIN,
      payload: user,
    });
  } catch (error) {
    alert("erro no servidor");
  }
};

const logout = () => (dispatch: any) => {
  localStorage.removeItem("belezixadmin.token");
  localStorage.removeItem("belezixadmin.refreshToken");
  localStorage.removeItem("belezixadmin.user");

  dispatch({
    type: LOGOUT,
  });
};

const initialState = {
  isAuthenticated: false,
  user: null as User | null,
};

const authReducer = (state = initialState, action: Action) => {
  switch (action.type) {
    case LOGIN:
      return {
        isAuthenticated: true,
        user: action.payload,
      };
    case LOGOUT:
      return {
        isAuthenticated: false,
        user: null,
      };
    default:
      return state;
  }
};

const rootReducer = combineReducers({
  auth: authReducer,
});

const store = createStore(rootReducer, applyMiddleware(thunk));

export default store;
export { login, logout };


Enter fullscreen mode Exit fullscreen mode

Com esse código, você pode utilizar o store do Redux para guardar o estado de autenticação e as informações do usuário logado.

O uso do Context API e do Redux são duas opções para gerenciamento de estado global na aplicação. A escolha entre esses dois pode depender de diversos fatores, como a complexidade da aplicação, a equipe de desenvolvimento e as preferências de cada um.

Algumas razões para o curso ter optado pelo uso do Context API em vez do Redux são:

  1. Mais simples: O Context API é mais fácil de entender e usar para iniciantes, pois é uma solução nativa do React e não requer a instalação de nenhuma biblioteca adicional.

  2. Menos verboso: O Context API é menos verboso do que o Redux, o que significa que é mais fácil de ler e manter.

  3. Boa performance: O Context API tem boa performance, especialmente em aplicações menores e com menor complexidade, pois não precisa passar informações por toda a árvore de componentes da aplicação.

  4. Focado no propósito: O Context API é uma solução mais direta e direcionada para o gerenciamento de estado global, enquanto o Redux é uma biblioteca mais abrangente e pode ser usada para vários propósitos.

Em resumo, o curso pode ter optado pelo uso do Context API por ser uma solução menos complexa, mais fácil de entender e com boa performance, especialmente para aplicações menores e com menor complexidade.



import Router from "next/router";
import type { NextPage } from "next";
import { Head, Flex, Text } from "shared/ui";
import { useAuth } from "shared/libs";
import { useEffect } from "react";

export const Login: NextPage = () => {
  const { isAuthenticated } = useAuth();
  useEffect(() => {
    if (isAuthenticated) {
      Router.push("/home");
    }
  }, [isAuthenticated]);
  return (
    <>
      <Head
        title="Belezix Admin | Login"
        description="Página de login do painel Admin do Belezix"
      />
      <Flex minW="100%" justifyContent="center">
        {!isAuthenticated && (
          <Flex mt="15%">
            <Text fontSize="6xl">CrazyStack ReactJs</Text>
          </Flex>
        )}
      </Flex>
    </>
  );
};


Enter fullscreen mode Exit fullscreen mode

Este código apresenta a implementação de uma página de login para uma aplicação React.

  1. A primeira coisa que temos é a importação do módulo Router do pacote next/router para redirecionar o usuário caso esteja autenticado.
  2. Em seguida, temos a importação do tipo NextPage do pacote next, que é utilizado para definir que esta página será uma página Next.js.
  3. Também temos as importações de componentes visuais, como o Head e o Flex e o Text, que são componentes customizados para renderizar elementos na página.
  4. Em seguida, temos a importação do useAuth da biblioteca shared/libs, que é uma função customizada para obter as informações de autenticação do usuário.
  5. Também temos a importação do useEffect do React, que é uma função de efeito para realizar operações de side-effects em reação a mudanças no estado da aplicação.

Na função de componente Login, temos a constante isAuthenticated que obtém a informação de autenticação do usuário através da função useAuth. Em seguida, usamos a função useEffect para verificar se o usuário está autenticado. Se estiver, redirecionamos para a página /home com a função Router.push.

Finalmente, retornamos o código HTML da página com o componente Flex como o container principal. Se o usuário não estiver autenticado, é exibido um título de texto. Caso contrário, nada é exibido.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .