Documentación de LoginGen

Introducción

LoginGen es un sitio web de plantillas para páginas de inicio de sesión. La razón detrás del desarrollo de este proyecto es que, aunque hay muchos sitios de plantillas en el mercado, la mayoría carece de soporte para páginas de inicio de sesión o solo ofrece una o dos plantillas simples, y en su mayoría no incluyen lógica frontend completa. Aunque en términos de peso de interacción, la página de inicio de sesión puede no ser tan importante como una Landing Page, una página de inicio de sesión atractiva puede mejorar la primera impresión que los usuarios tienen del sitio.

La pila tecnológica se basa principalmente en Next.js y Shadcn/ui. Además de proporcionar diseños de plantillas, también ofrece una lógica frontend completa y utiliza la capacidad de Server Actions de Next.js para unificar el procesamiento de autenticación en el servidor (excepto para el inicio de sesión social, que por conveniencia se maneja en eventos del lado del cliente).

Todo el código es abierto, al igual que Shadcn, por lo que puedes copiar y pegar directamente para usarlo en tu proyecto. También se recomienda modificar el código según las necesidades de tu proyecto. Para mayor comodidad, los componentes de formulario incluyen propiedades comunes que permiten un uso inmediato sin configuración adicional.

En el futuro, seguiremos añadiendo, mejorando y optimizando las plantillas. ¡Mantente atento!

Instalación

Las plantillas están desarrolladas sobre Next.js y Shadcn, por lo que primero necesitas tener configurado ese entorno:

Componentes de shadcn

Si utilizas otras herramientas de gestión de paquetes o prefieres instalarlos manualmente, consulta la documentación de shadcn.

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

Añade las siguientes animaciones CSS a tu archivo tailwind.config.js:

// para 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 sonner, necesitas añadir el siguiente componente:

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

Dependencias de terceros

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 -- Validación de formularios en el cliente.
  • motion -- Biblioteca de animaciones.
  • @zxcvbn-* -- Biblioteca para medir la fortaleza de contraseñas.
  • embla-carousel-* -- Biblioteca para carruseles (embla-carousel debe instalarse en devDependencies para obtener correctamente los tipos de TypeScript).
  • lucide-react -- Biblioteca de iconos.

Copiar y pegar código

Cada plantilla en la página de vista previa muestra el código completo. Solo necesitas copiar y pegar el código en la ubicación correspondiente de tu proyecto. Las imágenes incluidas en las plantillas pueden descargarse directamente, y los videos se proporcionan a través de CDN.

Por ahora, no hay soporte para instalación con un solo comando como npx, pero podría considerarse en el futuro.

Temas

Cada plantilla incluye un archivo styles/theme.module.css que contiene todas las variables de tema, estilos y animaciones necesarias. Puedes modificar este archivo para ajustar el tema o adaptarlo a la estética de tu sitio.

Modo oscuro

Para configurar el modo oscuro, sigue la documentación de shadcn. En el archivo theme.module.css de cada plantilla, el tema oscuro utiliza el prefijo CSS :global(.dark) para responder al modo oscuro global del sitio.

I18n (TODO)

Actualmente no hay soporte para configuración multilingüe, pero puedes añadirlo manualmente. En el futuro, se considerará agregar esta funcionalidad.

Propiedades de los formularios

Hay cinco formularios: inicio de sesión, registro, recuperación de contraseña, restablecimiento de contraseña y verificación OTP (one-time password). Cada formulario incluye parámetros para facilitar su uso inmediato, la mayoría de los cuales son comunes.

Aunque en el código de las plantillas, cada formulario es una ruta separada y la navegación entre ellos se maneja mediante redirecciones del servidor, todos los componentes están diseñados para usarse pasando propiedades, lo que significa que también puedes utilizar rutas del lado del cliente.

Inicio de sesión

La definición de la interfaz es la siguiente:

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>) // para mayor comodidad
  serverAction?: (
    formType: 'email' | 'password',
    currentState: SignInActionState,
    formData: FormData
  ) => Promise<SignInActionState>
  serverStateSuccessHandler?: (formType: 'email' | 'password', email: string) => void | Promise<void>
}
ParámetroValor predeterminadoExplicación
auths['social', 'password', 'email']Métodos de autenticación: inicio de sesión social, por correo electrónico o por contraseña.
socials['google', 'github']Tipos de inicio de sesión social (el diseño se ajusta automáticamente según la cantidad seleccionada y el ancho del formulario).
emailCorreo electrónico inicial. También puede usarse para prellenar el campo al volver desde el formulario de verificación OTP.
emailStrategy'magic-link'Tipo de correo electrónico enviado. Si es 'magic-link', no redirige y muestra una notificación; de lo contrario, redirige al formulario OTP.
socialHandlerFunción de callback al hacer clic en un botón de inicio de sesión social (puedes usar bibliotecas como Auth.js).
forgetHandler'#'Callback al hacer clic en "¿Olvidaste tu contraseña?" (se incluye por conveniencia).
serverActionTodos los formularios (excepto el inicio de sesión social) se envían al servidor mediante Server Actions.
serverStateSuccessHandlerNormalmente, tras la autenticación, se redirige desde el servidor, pero este callback permite manejar la redirección desde el cliente si es necesario.

Notas:

  1. socials -- Cada plantilla incluye un archivo config/social.tsx que puedes editar para añadir más métodos de inicio de sesión social. Puedes modificar el key para adaptarlo a tu framework de autenticación o cambiar los iconos SVG.
  2. email -- En el formulario verify-otp, hay un botón para editar el correo electrónico. Al hacer clic, el usuario vuelve a la página anterior para corregirlo. El parámetro email prellena este campo para facilitar la edición.

Registro

La definición de la interfaz es la siguiente:

export interface SignUpFormProps {
  email?: string
  serverAction: (currentState: SignUpActionState, formData: FormData) => Promise<SignUpActionState>
  serverStateSuccessHandler?: (email: string) => void | Promise<void>
}
ParámetroValor predeterminadoExplicación
emailCorreo electrónico inicial. También puede usarse para prellenar el campo al volver desde el formulario de verificación OTP.
serverActionEnvía los datos del formulario al servidor mediante Server Actions.
serverStateSuccessHandlerNormalmente, tras la autenticación, se redirige desde el servidor, pero este callback permite manejar la redirección desde el cliente si es necesario.

Recuperación de contraseña

La definición de la interfaz es la siguiente:

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 predeterminadoExplicación
emailCorreo electrónico inicial. También puede usarse para prellenar el campo al volver desde el formulario de verificación OTP.
emailStrategy'magic-link'Tipo de correo electrónico enviado. Si es 'magic-link', no redirige y muestra una notificación; de lo contrario, redirige al formulario OTP.
serverActionEnvía los datos del formulario al servidor mediante Server Actions.
serverStateSuccessHandlerNormalmente, tras la autenticación, se redirige desde el servidor, pero este callback permite manejar la redirección desde el cliente si es necesario.

Restablecimiento de contraseña

La definición de la interfaz es la siguiente:

export interface ResetPasswordFormProps {
  email?: string // Solo se usa para probar la fortaleza de la contraseña
  resetToken?: string
  serverAction: (
    resetToken: string,
    currentState: ResetPasswordActionState,
    formData: FormData
  ) => Promise<ResetPasswordActionState>
  serverStateSuccessHandler?: () => void | Promise<void>
}
ParámetroValor predeterminadoExplicación
emailEste correo solo se usa para probar la fortaleza de la contraseña (se recomienda configurarlo para mejorar la seguridad).
resetTokenToken generado por el servidor para rastrear el flujo del formulario.
serverActionEnvía los datos del formulario al servidor mediante Server Actions.
serverStateSuccessHandlerNormalmente, tras la autenticación, se redirige desde el servidor, pero este callback permite manejar la redirección desde el cliente si es necesario.

resetToken En el flujo de restablecimiento de contraseña, el servidor generalmente genera un token para rastrear y confirmar de forma segura al usuario que envía el formulario y su estado.

El servidor suele pasar el token de dos maneras: una es incluirlo en los parámetros de consulta de la URL y otra es colocarlo en una cookie, que el navegador enviará automáticamente al enviar el formulario.

En el primer caso, puedes usar searchParams o useSearchParams() para obtener el resetToken de los parámetros de consulta y pasarlo al formulario, que lo enviará automáticamente al Server Action del servidor al enviarse.

En el segundo caso, puedes ignorar este parámetro.

Verificación OTP

La definición de la interfaz es la siguiente:

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 predeterminadoExplicación
emailCorreo electrónico inicial que se muestra en la interfaz para que el usuario pueda editarlo fácilmente.
verifyTokenToken generado por el servidor para rastrear el flujo del formulario.
editEmailHandlerFunción de callback al hacer clic en el botón de editar correo electrónico.
codeResendActionFunción para reenviar el código (el intervalo en segundos se puede configurar con la constante code_resend_max_seconds en verify-otp-form.tsx).
serverActionEnvía los datos del formulario al servidor mediante Server Actions.
serverStateSuccessHandlerNormalmente, tras la autenticación, se redirige desde el servidor, pero este callback permite manejar la redirección desde el cliente si es necesario.

verifyToken En el flujo de verificación de contraseña de un solo uso (OTP), el servidor generalmente genera un token para rastrear y confirmar de forma segura al usuario que envía el formulario y su estado.

El servidor suele pasar el token de dos maneras: una es incluirlo en los parámetros de consulta de la URL y otra es colocarlo en una cookie, que el navegador enviará automáticamente al enviar el formulario.

En el primer caso, puedes usar searchParams o useSearchParams() para obtener el verifyToken de los parámetros de consulta y pasarlo al formulario, que lo enviará automáticamente al Server Action del servidor al enviarse.

En el segundo caso, puedes ignorar este parámetro.

Servidor

Excepto para el inicio de sesión social, todas las opciones de autenticación, después de la validación en el cliente, se envían a Server Actions para el procesamiento lógico en el servidor. Esto se maneja utilizando useFormState y useFormStatus de React para las operaciones de envío de formularios.

En el código de las plantillas, el archivo types/action-state.ts define un formato básico para los datos enviados a Server Action, que puedes ampliar según sea necesario.

Aquí hay un ejemplo de código para operaciones en server action:

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)
 
  // La validación del formulario también es necesaria en el 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
    }
  }
 
  // Realiza operaciones en el servidor, como enviar correos, acceder a bases de datos, etc.
 
  // La redirección puede hacerse desde el servidor
  revalidatePath('/auth/sign-in')
  redirect(`/auth/verify-otp`)
 
  // O devolverlo directamente al cliente
  // return { success: true }
}
 
async function passwordAction(formData: FormData): Promise<SignInActionState> {
  const data = Object.fromEntries(formData)
  // Simulación de retraso
  await new Promise((resolve) => setTimeout(resolve, 2000))
  console.log('Sign in password server action form data : ' + JSON.stringify(data, null, 2))
 
  return { success: true }
}