Documentação do LoginGen

Introdução

LoginGen é um site de modelos para páginas de login. O motivo para o desenvolvimento deste projeto é que, embora existam muitos sites de modelos no mercado, a maioria carece de suporte para páginas de login ou oferece apenas um ou dois modelos simples, além de não possuir lógica frontend completa. Embora, em termos de peso de interação, a página de login possa não ser tão importante quanto uma Landing Page, uma página de login bonita também pode melhorar a primeira impressão do usuário sobre o site.

A stack de tecnologia é baseada principalmente em Next.js e Shadcn/ui. Além de fornecer designs de modelos, também oferece lógica frontend completa e utiliza a capacidade de Server Actions do Next.js para processamento unificado de autenticação no servidor (exceto para login social, que, por conveniência, foi colocado em eventos do cliente).

Todo o código é aberto, assim como o Shadcn, permitindo copiar e colar diretamente para uso. Portanto, você pode modificar o código de acordo com as necessidades do seu projeto, e isso é recomendado. Claro, para maior conveniência, os componentes de formulário também incluem algumas propriedades comuns, permitindo uso imediato.

No futuro, continuaremos a adicionar, aprimorar e otimizar os modelos. Fiquem atentos.

Instalação

Os modelos são desenvolvidos com base no Next.js e no Shadcn, portanto, primeiro você precisa ter o ambiente do projeto configurado:

Componentes do shadcn

Se você estiver usando outras ferramentas de comando de pacotes ou quiser instalar manualmente, consulte a documentação do shadcn.

pnpm dlx shadcn@latest add card tabs form button input input-otp separator sonner

Adicione as seguintes animações CSS ao seu arquivo tailwind.config.js:

// for input-otp
/** @type {import('tailwindcss').Config} */
module.exports = {
  theme: {
    extend: {
      keyframes: {
        'caret-blink': {
          '0%,70%,100%': { opacity: '1' },
          '20%,50%': { opacity: '0' }
        }
      },
      animation: {
        'caret-blink': 'caret-blink 1.25s ease-out infinite'
      }
    }
  }
}

Para o sonner, você precisa adicionar o seguinte componente:

import { Toaster } from '@/components/ui/sonner'
 
export default function RootLayout({ children }) {
  return (
    <html lang='en'>
      <head />
      <body>
        <main>{children}</main>
        <Toaster />
      </body>
    </html>
  )
}

Bibliotecas de dependências de terceiros

pnpm add react-hook-form@7.52.2 zod motion @zxcvbn-ts/core @zxcvbn-ts/language-common @zxcvbn-ts/language-en embla-carousel-react embla-carousel-autoplay embla-carousel-fade lucide-react
pnpm add -D embla-carousel
  • react-hook-form + zod -- Validação de formulário no cliente
  • motion -- Biblioteca de animação
  • @zxcvbn-* -- Biblioteca de verificação de força de senha
  • embla-carousel-* -- Biblioteca de carrossel (onde embla-carousel precisa ser instalado em devDependencies para obter os tipos TypeScript corretamente)
  • lucide-react -- Biblioteca de ícones

Copiar e colar código

Cada página de visualização do modelo possui uma exibição completa do código. Basta copiar e colar o código necessário na posição correspondente do seu projeto. As imagens incluídas nos modelos podem ser baixadas diretamente, e os vídeos são fornecidos via CDN.

Atualmente, não há suporte para instalação automática semelhante ao npx, mas isso pode ser considerado no futuro.

Tema

Cada modelo possui um arquivo de tema styles/theme.module.css, que contém todas as variáveis de tema, estilos e animações necessárias para o modelo. Você pode modificar os estilos neste arquivo para ajustar o tema ou adequá-lo à estética da UI do seu site.

Modo escuro

Para o modo escuro, siga a documentação do shadcn. Nos arquivos de tema theme.module.css de cada modelo, o tema escuro utiliza o prefixo de seletor CSS :global(.dark) para responder ao modo escuro global do site.

I18n (TODO)

Atualmente, não há suporte para configuração de múltiplos idiomas, mas você ainda pode adicioná-lo manualmente, embora possa ser um pouco trabalhoso. No futuro, consideraremos adicionar essa funcionalidade.

Propriedades do formulário

Existem cinco formulários: login, registro, recuperação de senha, redefinição de senha e verificação OTP (one-time password). Cada formulário possui alguns parâmetros para facilitar o uso imediato, e a maioria dos parâmetros são comuns.

Embora, no código do modelo, cada página de formulário seja uma rota separada, com redirecionamento no servidor para navegação entre as páginas de formulário, todos os componentes são projetados para serem usados com propriedades. Isso significa que você pode perfeitamente usar rotas no cliente.

Login

A definição da interface é a seguinte:

const _auths = ['social', 'email', 'password'] as const
type Auth = (typeof _auths)[number]
 
export interface SignInFormProps {
  auths?: Readonly<Auth[]>
  socials?: SocialKey[]
  email?: string
  emailStrategy?: 'magic-link' | 'one-time-pwd'
  socialHandler?: (event: MouseEvent<HTMLButtonElement>, key: SocialKey) => void | Promise<void>
  forgetHandler?: string | ((event: React.MouseEvent<HTMLAnchorElement>) => void | Promise<void>) // for more convenient use
  serverAction?: (
    formType: 'email' | 'password',
    currentState: SignInActionState,
    formData: FormData
  ) => Promise<SignInActionState>
  serverStateSuccessHandler?: (formType: 'email' | 'password', email: string) => void | Promise<void>
}
ParâmetroValor padrãoExplicação
auths['social', 'password', 'email']Métodos de autenticação: login social, login por e-mail, login por senha
socials['google', 'github']Tipos de login social (o layout é ajustado automaticamente com base no número selecionado e na largura do formulário)
emailE-mail inicial, também pode ser usado para pré-preencher o e-mail após o redirecionamento do formulário de verificação OTP
emailStrategy'magic-link'Tipo de e-mail enviado. Se for 'magic-link', não redireciona a página, mas aciona um pop-up sonner; caso contrário, deve redirecionar para o formulário de verificação OTP
socialHandlerFunção de retorno ao clicar no botão de login social. Você pode usar bibliotecas como Auth.js para implementar
forgetHandler'#'Função de retorno ao clicar no botão "Esqueci a senha". Este parâmetro é fornecido principalmente para conveniência
serverActionTodos os formulários, exceto login social, são enviados ao servidor via Server Actions
serverStateSuccessHandlerGeralmente, após a autenticação no servidor, o redirecionamento é feito diretamente no servidor. Mas, se você quiser implementar alguma lógica de redirecionamento no cliente, pode fazê-lo neste parâmetro de retorno

Observações:

  1. socials -- Cada modelo possui um arquivo de configuração config/social.tsx. Você pode editar este arquivo para adicionar os métodos de login social que desejar. Você pode modificar livremente a key para se adequar à estrutura de autenticação que estiver usando ou alterar o ícone SVG para o de sua preferência.
  2. email -- Na UI do formulário verify-otp, há um botão para editar o e-mail. Quando o usuário clica nele, deve retornar à página anterior para permitir que o usuário corrija o e-mail. O parâmetro email é projetado para pré-preencher o e-mail exibido após esse retorno.

Registro

A definição da interface é a seguinte:

export interface SignUpFormProps {
  email?: string
  serverAction: (currentState: SignUpActionState, formData: FormData) => Promise<SignUpActionState>
  serverStateSuccessHandler?: (email: string) => void | Promise<void>
}
ParâmetroValor padrãoExplicação
emailE-mail inicial, também pode ser usado para pré-preencher o e-mail após o redirecionamento do formulário de verificação OTP
serverActionEnvia os dados do formulário para o servidor via Server Actions
serverStateSuccessHandlerGeralmente, após a autenticação no servidor, o redirecionamento é feito diretamente no servidor. Mas, se você quiser implementar alguma lógica de redirecionamento no cliente, pode fazê-lo neste parâmetro de retorno

Esqueci a senha

A definição da interface é a seguinte:

export interface ForgetPasswordProps {
  email?: string
  emailStrategy?: 'magic-link' | 'one-time-pwd'
  serverAction: (
    currentState: ForgetPasswordActionState,
    formData: FormData
  ) => Promise<ForgetPasswordActionState>
  serverStateSuccessHandler?: (email: string) => void | Promise<void>
}
ParâmetroValor padrãoExplicação
emailE-mail inicial, também pode ser usado para pré-preencher o e-mail após o redirecionamento do formulário de verificação OTP
emailStrategy'magic-link'Tipo de e-mail enviado. Se for 'magic-link', não redireciona a página, mas aciona um pop-up sonner; caso contrário, deve redirecionar para o formulário de verificação OTP
serverActionEnvia os dados do formulário para o servidor via Server Actions
serverStateSuccessHandlerGeralmente, após a autenticação no servidor, o redirecionamento é feito diretamente no servidor. Mas, se você quiser implementar alguma lógica de redirecionamento no cliente, pode fazê-lo neste parâmetro de retorno

Redefinir senha

A definição da interface é a seguinte:

export interface ResetPasswordFormProps {
  email?: string // Only used to test password strength
  resetToken?: string
  serverAction: (
    resetToken: string,
    currentState: ResetPasswordActionState,
    formData: FormData
  ) => Promise<ResetPasswordActionState>
  serverStateSuccessHandler?: () => void | Promise<void>
}
ParâmetroValor padrãoExplicação
emailEste e-mail é usado apenas para testar a força da senha (recomenda-se configurá-lo para melhorar a força da senha inserida pelo usuário)
resetTokenToken gerado pelo servidor para rastrear o fluxo do formulário
serverActionEnvia os dados do formulário para o servidor via Server Actions
serverStateSuccessHandlerGeralmente, após a autenticação no servidor, o redirecionamento é feito diretamente no servidor. Mas, se você quiser implementar alguma lógica de redirecionamento no cliente, pode fazê-lo neste parâmetro de retorno

resetToken No fluxo de redefinição de senha, geralmente o servidor gera um token para rastrear e confirmar com segurança o remetente e o estado atual do formulário.

O servidor geralmente passa o token de duas maneiras: uma é colocá-lo nos parâmetros de consulta da URL, e a outra é colocá-lo em um cookie, que o navegador enviará automaticamente quando o formulário for enviado.

No primeiro caso, você pode usar searchParams ou useSearchParams() para obter o resetToken nos parâmetros de consulta e passá-lo para o formulário, que o enviará automaticamente para a Server Action do servidor quando for submetido.

No segundo caso, você pode ignorar este parâmetro.

Verificação OTP

A definição da interface é a seguinte:

export interface VerifyOTPFormProps {
  email: string
  verifyToken?: string
  editEmailHandler?: (event: MouseEvent<HTMLButtonElement>) => void
  serverAction: (
    email: string,
    verifyToken: string,
    currentState: VerifyOTPActionState,
    formData: FormData
  ) => Promise<VerifyOTPActionState>
  codeResendAction: (email: string, verifyToken: string) => Promise<VerifyOTPActionState>
  serverStateSuccessHandler?: () => void | Promise<void>
}
ParâmetroValor padrãoExplicação
emailE-mail inicial, que será exibido na UI para permitir que o usuário o edite facilmente
verifyTokenToken gerado pelo servidor para rastrear o fluxo do formulário
editEmailHandlerFunção de evento quando o botão de edição de e-mail é clicado
codeResendActionFunção de evento para reenviar o código (o intervalo em segundos pode ser definido pela constante code_resend_max_seconds em verify-otp-form.tsx)
serverActionEnvia os dados do formulário para o servidor via Server Actions
serverStateSuccessHandlerGeralmente, após a autenticação no servidor, o redirecionamento é feito diretamente no servidor. Mas, se você quiser implementar alguma lógica de redirecionamento no cliente, pode fazê-lo neste parâmetro de retorno

verifyToken No fluxo de verificação de senha única (one-time password), geralmente o servidor gera um token para rastrear e confirmar com segurança o remetente e o estado atual do formulário.

O servidor geralmente passa o token de duas maneiras: uma é colocá-lo nos parâmetros de consulta da URL, e a outra é colocá-lo em um cookie, que o navegador enviará automaticamente quando o formulário for enviado.

No primeiro caso, você pode usar searchParams ou useSearchParams() para obter o verifyToken nos parâmetros de consulta e passá-lo para o formulário, que o enviará automaticamente para a Server Action do servidor quando for submetido.

No segundo caso, você pode ignorar este parâmetro.

Servidor

Exceto para login social, todas as outras opções de login, após a validação no cliente, são enviadas para Server Actions para processamento lógico no servidor. O envio do formulário é realizado usando useFormState e useFormStatus do React.

No arquivo types/action-state.ts do código do modelo, há uma definição simples do formato dos dados enviados para a Server Action, que você pode expandir conforme necessário.

Aqui está um exemplo de código para operações em server actions:

sign-in-action.ts

'use server'
 
import type { FormType, SignInActionState } from '@/types/server'
import { redirect } from 'next/navigation'
import { revalidatePath } from 'next/cache'
 
export async function signInAction(
  formType: FormType,
  currentState: SignInActionState,
  formData: FormData
): Promise<SignInActionState> {
  switch (formType) {
    case 'email':
      return await emailAction(formData)
    case 'password':
      return await passwordAction(formData)
  }
}
 
async function emailAction(formData: FormData): Promise<SignInActionState> {
  const data = Object.fromEntries(formData)
  const parsed = signInSchemas.email.safeParse(data)
 
  // A validação do formulário também é necessária no lado do servidor
  if (!parsed.success) {
    const issues: Record<string, string> = {}
    parsed.error.issues.map((issue) => (issues[issue.path[0]] = issue.message))
    return {
      success: false,
      formIssues: issues
    }
  }
 
  // Realize algumas operações no servidor, como enviar e-mails, operar bancos de dados, etc.
 
  // O redirecionamento pode ser feito no servidor
  revalidatePath('/auth/sign-in')
  redirect(`/auth/verify-otp`)
 
  // Ou retorne diretamente para o cliente
  // return { success: true }
}
 
async function passwordAction(formData: FormData): Promise<SignInActionState> {
  const data = Object.fromEntries(formData)
  // simular atraso
  await new Promise((resolve) => setTimeout(resolve, 2000))
  console.log('Sign in password server action form data : ' + JSON.stringify(data, null, 2))
 
  return { success: true }
}