LoginGen 문서

소개

LoginGen은 로그인 페이지 템플릿 웹사이트입니다. 이 프로젝트를 개발한 이유는 시중에 많은 템플릿 웹사이트가 있지만 대부분 로그인 페이지 지원이 부족하거나 단순히 한두 개의 템플릿만 제공하며, 완전한 프론트엔드 로직을 갖추지 않았기 때문입니다. 비록 상호작용의 중요도에서는 랜딩 페이지만큼 중요하지 않을 수 있지만, 아름다운 로그인 페이지는 사용자에게 웹사이트에 대한 첫인상을 깊게 할 수 있습니다.

기술 스택은 주로 Next.jsShadcn/ui를 기반으로 개발되었으며, 템플릿 디자인뿐만 아니라 완전한 프론트엔드 로직을 제공하고 Next.js의 Server Actions 기능을 사용하여 통합된 서버 측 인증 처리를 수행합니다 (소셜 로그인은 편의를 위해 클라이언트 이벤트에 포함되었습니다).

모든 코드는 Shadcn처럼 바로 복사하여 사용할 수 있도록 공개되어 있습니다. 따라서 프로젝트 요구사항에 따라 코드를 자유롭게 수정할 수 있으며, 이것이 권장됩니다. 물론 편의를 위해 폼 컴포넌트에는 일반적으로 사용되는 속성들이 추출되어 있어 바로 사용할 수 있습니다.

앞으로 지속적으로 템플릿을 추가, 개선, 최적화할 예정이니 기대해주세요.

설치

템플릿은 Next.js와 Shadcn을 기반으로 개발되었으므로 먼저 해당 프로젝트 환경이 필요합니다:

shadcn 컴포넌트

다른 패키지 관리 도구를 사용하거나 수동으로 설치하려면 shadcn 문서를 참조하세요.

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

다음 CSS 애니메이션을 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'
      }
    }
  }
}

sonner를 사용하려면 다음 컴포넌트를 추가해야 합니다.

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

서드파티 종속성 라이브러리

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 -- 폼 클라이언트 측 유효성 검사
  • motion -- 애니메이션 라이브러리
  • @zxcvbn-* -- 비밀번호 강도 검사 라이브러리
  • embla-carousel-* -- 캐러셀 라이브러리 (embla-carousel은 TypeScript 타입을 올바르게 가져오기 위해 devDependencies에 설치해야 합니다)
  • lucide-react -- 아이콘 라이브러리

코드 복사 및 붙여넣기

각 템플릿의 미리보기 페이지에는 전체 코드가 표시됩니다. 필요한 코드를 복사하여 프로젝트의 해당 위치에 붙여넣기만 하면 됩니다. 템플릿에 포함된 이미지는 원본을 직접 다운로드할 수 있으며, 비디오는 CDN 방식으로 제공됩니다.

현재는 npx와 같은 원클릭 설치 기능을 지원하지 않으며, 추후에 고려할 예정입니다.

테마

각 템플릿에는 styles/theme.module.css 테마 파일이 있습니다. 이 파일에는 모든 테마 변수와 해당 템플릿에 필요한 스타일, 애니메이션 등이 포함되어 있습니다. 이 파일의 스타일을 수정하여 테마를 변경하거나 웹사이트 UI에 맞게 조정할 수 있습니다.

다크 모드

다크 모드는 shadcn의 문서에 따라 구성하세요. 각 템플릿의 theme.module.css 테마 파일에서 다크 테마는 :global(.dark) CSS 선택자 접두사를 사용하여 웹사이트의 전역 다크 모드에 반응합니다.

I18n(TODO)

현재는 다국어 구성을 지원하지 않지만, 수동으로 추가할 수 있습니다. 약간 번거로울 수 있지만 추후에 이 기능을 추가할 예정입니다.

폼 속성

로그인, 회원가입, 비밀번호 찾기, 비밀번호 재설정, OTP(one-time password) 인증 등 총 다섯 가지 폼이 있으며, 각 폼에는 바로 사용할 수 있도록 몇 가지 매개변수가 있습니다. 대부분의 매개변수는 공통적으로 사용됩니다.

템플릿 코드에서 기본적으로 각 폼 페이지는 별도의 라우트이며, 서버 측 리디렉션을 통해 폼 페이지 라우트 간에 네비게이션합니다. 그러나 모든 컴포넌트는 속성 전달 방식으로 설계되었으므로 클라이언트 측 라우팅을 완전히 사용할 수 있습니다.

로그인

인터페이스 정의는 다음과 같습니다:

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>
}
매개변수기본값설명
auths['social', 'password', 'email']인증 방식: 소셜 로그인, 이메일 로그인, 비밀번호 로그인
socials['google', 'github']소셜 로그인 유형 (레이아웃은 선택한 수와 폼의 너비에 따라 자동으로 조정됩니다)
email초기화 이메일, OTP 인증 폼에서 이메일 수정 후 돌아갈 때 미리 채워진 이메일을 표시하는 데도 사용됩니다
emailStrategy'magic-link'이메일 전송 유형, 'magic-link'인 경우 페이지를 이동하지 않고 sonner 팝업을 트리거하며, 그렇지 않으면 OTP 인증 폼으로 이동해야 합니다
socialHandler소셜 로그인 버튼 클릭 시 콜백 함수, Auth.js와 같은 라이브러리를 사용하여 구현할 수 있습니다
forgetHandler'#''비밀번호 찾기' 버튼 클릭 시 콜백, 주로 편의를 위해 이 매개변수를 사용합니다
serverAction소셜 로그인을 제외한 모든 폼은 Server Actions를 통해 서버 측으로 제출됩니다
serverStateSuccessHandler일반적으로 서버 측 인증이 완료되면 서버 측에서 바로 리디렉션하지만, 클라이언트 측에서 리디렉션 로직을 구현하려면 이 콜백 매개변수에서 구현할 수 있습니다

설명:

  1. socials -- 각 템플릿에는 config/social.tsx 구성 파일이 있습니다. 이 파일을 편집하여 필요한 소셜 로그인 방식을 확장할 수 있습니다. 사용하는 인증 프레임워크에 맞게 key를 자유롭게 수정하거나 원하는 아이콘 SVG로 변경할 수 있습니다.
  2. email -- verify-otp 폼 UI에는 이메일을 수정할 수 있는 버튼이 있습니다. 사용자가 버튼을 클릭하면 이전 페이지로 돌아가 올바른 이메일로 수정할 수 있습니다. 이 email 속성은 돌아간 후 미리 채워진 이메일을 표시하기 위해 설계되었습니다.

회원가입

인터페이스 정의는 다음과 같습니다:

export interface SignUpFormProps {
  email?: string
  serverAction: (currentState: SignUpActionState, formData: FormData) => Promise<SignUpActionState>
  serverStateSuccessHandler?: (email: string) => void | Promise<void>
}
매개변수기본값설명
email초기화 이메일, OTP 인증 폼에서 이메일 수정 후 돌아갈 때 미리 채워진 이메일을 표시하는 데도 사용됩니다
serverActionServer Actions를 통해 폼 데이터를 서버 측으로 제출합니다
serverStateSuccessHandler일반적으로 서버 측 인증이 완료되면 서버 측에서 바로 리디렉션하지만, 클라이언트 측에서 리디렉션 로직을 구현하려면 이 콜백 매개변수에서 구현할 수 있습니다

비밀번호 찾기

인터페이스 정의는 다음과 같습니다:

export interface ForgetPasswordProps {
  email?: string
  emailStrategy?: 'magic-link' | 'one-time-pwd'
  serverAction: (
    currentState: ForgetPasswordActionState,
    formData: FormData
  ) => Promise<ForgetPasswordActionState>
  serverStateSuccessHandler?: (email: string) => void | Promise<void>
}
매개변수기본값설명
email초기화 이메일, OTP 인증 폼에서 이메일 수정 후 돌아갈 때 미리 채워진 이메일을 표시하는 데도 사용됩니다
emailStrategy'magic-link'이메일 전송 유형, 'magic-link'인 경우 페이지를 이동하지 않고 sonner 팝업을 트리거하며, 그렇지 않으면 OTP 인증 폼으로 이동해야 합니다
serverActionServer Actions를 통해 폼 데이터를 서버 측으로 제출합니다
serverStateSuccessHandler일반적으로 서버 측 인증이 완료되면 서버 측에서 바로 리디렉션하지만, 클라이언트 측에서 리디렉션 로직을 구현하려면 이 콜백 매개변수에서 구현할 수 있습니다

비밀번호 재설정

인터페이스 정의는 다음과 같습니다:

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>
}
매개변수기본값설명
email이 이메일은 비밀번호 강도 테스트에만 사용됩니다 (사용자가 입력한 비밀번호의 강도를 높이기 위해 설정하는 것이 좋습니다)
resetToken서버에서 생성된 폼 프로세스를 추적하기 위한 token
serverActionServer Actions를 통해 폼 데이터를 서버 측으로 제출합니다
serverStateSuccessHandler일반적으로 서버 측 인증이 완료되면 서버 측에서 바로 리디렉션하지만, 클라이언트 측에서 리디렉션 로직을 구현하려면 이 콜백 매개변수에서 구현할 수 있습니다

resetToken 비밀번호 재설정 프로세스에서 서버는 일반적으로 현재 폼 제출자와 상태를 안전하게 추적하고 확인하기 위해 token을 생성합니다.

서버는 일반적으로 두 가지 방식으로 token을 전달합니다: 하나는 URL의 쿼리 매개변수에 넣는 것이고, 다른 하나는 cookie에 넣는 것입니다. 폼이 제출될 때 브라우저는 현재 cookie를 자동으로携带합니다.

전자의 경우 searchParams 또는 useSearchParams()를 사용하여 쿼리 매개변수에서 resetToken을 가져와 폼에 전달할 수 있으며, 폼은 제출 시 이 resetToken을 서버 측 Server Action에 자동으로 전달합니다.

후자의 경우 이 매개변수를 무시할 수 있습니다.

OTP 인증

인터페이스 정의는 다음과 같습니다:

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>
}
매개변수기본값설명
email초기화 이메일이며 이 이메일은 UI에 표시되어 사용자가 편집할 수 있습니다
verifyToken서버에서 생성된 폼 프로세스를 추적하기 위한 token
editEmailHandler이메일 편집 버튼 클릭 시 이벤트 함수
codeResendAction코드 재전송 이벤트 함수 (초 단위는 verify-otp-form.tsxcode_resend_max_seconds 상수로 설정할 수 있습니다)
serverActionServer Actions를 통해 폼 데이터를 서버 측으로 제출합니다
serverStateSuccessHandler일반적으로 서버 측 인증이 완료되면 서버 측에서 바로 리디렉션하지만, 클라이언트 측에서 리디렉션 로직을 구현하려면 이 콜백 매개변수에서 구현할 수 있습니다

verifyToken one-time password 인증 프로세스에서 서버는 일반적으로 현재 폼 제출자와 상태를 안전하게 추적하고 확인하기 위해 token을 생성합니다.

서버는 일반적으로 두 가지 방식으로 token을 전달합니다: 하나는 URL의 쿼리 매개변수에 넣는 것이고, 다른 하나는 cookie에 넣는 것입니다. 폼이 제출될 때 브라우저는 현재 cookie를 자동으로携带합니다.

전자의 경우 searchParams 또는 useSearchParams()를 사용하여 쿼리 매개변수에서 verifyToken을 가져와 폼에 전달할 수 있으며, 폼은 제출 시 이 verifyToken을 서버 측 Server Action에 자동으로 전달합니다.

후자의 경우 이 매개변수를 무시할 수 있습니다.

서버 측

소셜 로그인을 제외한 모든 로그인 옵션은 클라이언트 측 유효성 검사가 완료된 후 Server Actions에 통합되어 서버 측 로직 처리를 위해 제출됩니다. React의 useFormStateuseFormStatus를 사용하여 폼 제출 작업을 수행합니다.

템플릿 코드의 types/action-state.ts에는 Server Action에 제출되는 데이터 형식이 간단히 정의되어 있으며, 필요에 따라 확장할 수 있습니다.

다음은 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)
 
  // 서버 측에서도 폼 유효성 검사가 필요합니다
  if (!parsed.success) {
    const issues: Record<string, string> = {}
    parsed.error.issues.map((issue) => (issues[issue.path[0]] = issue.message))
    return {
      success: false,
      formIssues: issues
    }
  }
 
  // 이메일 전송, 데이터베이스 조작 등과 같은 서버 측 작업을 수행합니다.
 
  // 서버 측에서 리디렉션을 수행할 수 있습니다
  revalidatePath('/auth/sign-in')
  redirect(`/auth/verify-otp`)
 
  // 또는 클라이언트에 직접 반환할 수 있습니다
  // return { success: true }
}
 
async function passwordAction(formData: FormData): Promise<SignInActionState> {
  const data = Object.fromEntries(formData)
  // 모의 지연
  await new Promise((resolve) => setTimeout(resolve, 2000))
  console.log('Sign in password server action form data : ' + JSON.stringify(data, null, 2))
 
  return { success: true }
}