'use client'; import { useState, useEffect } from "react"; import { cn } from "@/lib/utils" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Ship, InfoIcon, GlobeIcon, ChevronDownIcon, ArrowLeft } from "lucide-react"; import { useToast } from "@/hooks/use-toast"; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuRadioItem } from "@/components/ui/dropdown-menu" import { useTranslation } from '@/i18n/client' import { useRouter, useParams, usePathname, useSearchParams } from 'next/navigation'; import { signIn, getSession } from 'next-auth/react'; import { buttonVariants } from "@/components/ui/button" import Link from "next/link" import Image from 'next/image'; import { useFormState } from 'react-dom'; import { InputOTP, InputOTPGroup, InputOTPSlot, } from "@/components/ui/input-otp" import { requestPasswordResetAction } from "@/lib/users/auth/partners-auth"; type LoginMethod = 'username' | 'sgips'; export function LoginForm({ className, ...props }: React.ComponentProps<"div">) { const params = useParams() || {}; const pathname = usePathname() || ''; const router = useRouter(); const searchParams = useSearchParams(); const lng = params.lng as string; const { t, i18n } = useTranslation(lng, 'login'); const { toast } = useToast(); // 상태 관리 const [loginMethod, setLoginMethod] = useState('username'); const [isLoading, setIsLoading] = useState(false); const [showForgotPassword, setShowForgotPassword] = useState(false); // MFA 관련 상태 const [showMfaForm, setShowMfaForm] = useState(false); const [mfaToken, setMfaToken] = useState(''); const [mfaUserId, setMfaUserId] = useState(''); const [mfaUserEmail, setMfaUserEmail] = useState(''); const [mfaCountdown, setMfaCountdown] = useState(0); // 일반 로그인 폼 데이터 const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); // S-Gips 로그인 폼 데이터 const [sgipsUsername, setSgipsUsername] = useState(''); const [sgipsPassword, setSgipsPassword] = useState(''); // 서버 액션 상태 const [passwordResetState, passwordResetAction] = useFormState(requestPasswordResetAction, { success: false, error: undefined, message: undefined, }); const handleChangeLanguage = (lang: string) => { const segments = pathname.split('/'); segments[1] = lang; router.push(segments.join('/')); }; const currentLanguageText = i18n.language === 'ko' ? t('languages.korean') : t('languages.english'); const goToVendorRegistration = () => { router.push(`/${lng}/partners/repository`); }; // MFA 카운트다운 효과 useEffect(() => { if (mfaCountdown > 0) { const timer = setTimeout(() => setMfaCountdown(mfaCountdown - 1), 1000); return () => clearTimeout(timer); } }, [mfaCountdown]); // 서버 액션 결과 처리 useEffect(() => { if (passwordResetState.success && passwordResetState.message) { toast({ title: '재설정 링크 전송', description: passwordResetState.message, }); setShowForgotPassword(false); } else if (passwordResetState.error) { toast({ title: t('errorTitle'), description: passwordResetState.error, variant: 'destructive', }); } }, [passwordResetState, toast, t]); // SMS 토큰 전송 const handleSendSms = async () => { if (!mfaUserId || mfaCountdown > 0) return; setIsLoading(true); try { // SMS 전송 API 호출 (실제 구현 필요) const response = await fetch('/api/auth/send-sms', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: mfaUserId }), }); if (response.ok) { setMfaCountdown(60); // 60초 카운트다운 toast({ title: 'SMS 전송 완료', description: '인증번호를 전송했습니다.', }); } else { toast({ title: t('errorTitle'), description: 'SMS 전송에 실패했습니다.', variant: 'destructive', }); } } catch (error) { console.error('SMS send error:', error); toast({ title: t('errorTitle'), description: 'SMS 전송 중 오류가 발생했습니다.', variant: 'destructive', }); } finally { setIsLoading(false); } }; // MFA 토큰 검증 const handleMfaSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!mfaToken || mfaToken.length !== 6) { toast({ title: t('errorTitle'), description: '6자리 인증번호를 입력해주세요.', variant: 'destructive', }); return; } setIsLoading(true); try { // MFA 토큰 검증 API 호출 const response = await fetch('/api/auth/verify-mfa', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ userId: mfaUserId, token: mfaToken }), }); if (response.ok) { toast({ title: '인증 완료', description: '로그인이 완료되었습니다.', }); // callbackUrl 처리 const callbackUrlParam = searchParams?.get('callbackUrl'); if (callbackUrlParam) { try { const callbackUrl = new URL(callbackUrlParam); const relativeUrl = callbackUrl.pathname + callbackUrl.search; router.push(relativeUrl); } catch (e) { router.push(callbackUrlParam); } } else { router.push(`/${lng}/partners/dashboard`); } } else { const errorData = await response.json(); toast({ title: t('errorTitle'), description: errorData.message || '인증번호가 올바르지 않습니다.', variant: 'destructive', }); } } catch (error) { console.error('MFA verification error:', error); toast({ title: t('errorTitle'), description: 'MFA 인증 중 오류가 발생했습니다.', variant: 'destructive', }); } finally { setIsLoading(false); } }; // 일반 사용자명/패스워드 로그인 처리 (간소화된 버전) const handleUsernameLogin = async (e: React.FormEvent) => { e.preventDefault(); if (!username || !password) { toast({ title: t('errorTitle'), description: t('credentialsRequired'), variant: 'destructive', }); return; } setIsLoading(true); try { // NextAuth credentials-password provider로 로그인 const result = await signIn('credentials-password', { username: username, password: password, redirect: false, }); if (result?.ok) { // 로그인 1차 성공 - 바로 MFA 화면으로 전환 toast({ title: t('loginSuccess'), description: '1차 인증이 완료되었습니다.', }); // 모든 사용자는 MFA 필수이므로 바로 MFA 폼으로 전환 setMfaUserId(username); // 입력받은 username 사용 setMfaUserEmail(username); // 입력받은 username 사용 (보통 이메일) setShowMfaForm(true); // 자동으로 SMS 전송 setTimeout(() => { handleSendSms(); }, 500); toast({ title: 'SMS 인증 필요', description: '등록된 전화번호로 인증번호를 전송합니다.', }); } else { // 로그인 실패 처리 let errorMessage = t('invalidCredentials'); if (result?.error) { switch (result.error) { case 'CredentialsSignin': errorMessage = t('invalidCredentials'); break; case 'AccessDenied': errorMessage = t('accessDenied'); break; default: errorMessage = t('defaultErrorMessage'); } } toast({ title: t('errorTitle'), description: errorMessage, variant: 'destructive', }); } } catch (error) { console.error('S-GIPS Login error:', error); toast({ title: t('errorTitle'), description: t('defaultErrorMessage'), variant: 'destructive', }); } finally { setIsLoading(false); } }; // S-Gips 로그인 처리 // S-Gips 로그인 처리 (간소화된 버전) const handleSgipsLogin = async (e: React.FormEvent) => { e.preventDefault(); if (!sgipsUsername || !sgipsPassword) { toast({ title: t('errorTitle'), description: t('credentialsRequired'), variant: 'destructive', }); return; } setIsLoading(true); try { // NextAuth credentials-password provider로 로그인 (S-Gips 구분) const result = await signIn('credentials-password', { username: sgipsUsername, password: sgipsPassword, provider: 'sgips', // S-Gips 구분을 위한 추가 파라미터 redirect: false, }); if (result?.ok) { // S-Gips 1차 인증 성공 - 바로 MFA 화면으로 전환 toast({ title: t('loginSuccess'), description: 'S-Gips 인증이 완료되었습니다.', }); // S-Gips도 MFA 필수이므로 바로 MFA 폼으로 전환 setMfaUserId(sgipsUsername); setMfaUserEmail(sgipsUsername); setShowMfaForm(true); // 자동으로 SMS 전송 setTimeout(() => { handleSendSms(); }, 500); toast({ title: 'SMS 인증 시작', description: 'S-Gips 등록 전화번호로 인증번호를 전송합니다.', }); } else { let errorMessage = t('sgipsLoginFailed'); if (result?.error) { switch (result.error) { case 'CredentialsSignin': errorMessage = t('invalidSgipsCredentials'); break; case 'AccessDenied': errorMessage = t('sgipsAccessDenied'); break; default: errorMessage = t('sgipsSystemError'); } } toast({ title: t('errorTitle'), description: errorMessage, variant: 'destructive', }); } } catch (error) { console.error('S-Gips login error:', error); toast({ title: t('errorTitle'), description: t('sgipsSystemError'), variant: 'destructive', }); } finally { setIsLoading(false); } }; return (
{/* Left Content */}
{/* Top bar with Logo + eVCP (left) and "Request Vendor Repository" (right) */}
eVCP
{'업체 등록 신청'}
{/* Content section that occupies remaining space, centered vertically */}
{/* Header */}
{!showMfaForm ? ( <>

{t('loginMessage')}

{'등록된 업체만 로그인하실 수 있습니다. 아직 등록되지 않은 업체라면 상단의 업체 등록 신청 버튼을 이용해주세요.'}

) : ( <>
🔐

SMS 인증

{mfaUserEmail}로 로그인하셨습니다

등록된 전화번호로 전송된 6자리 인증번호를 입력해주세요

)}
{/* 로그인 폼 또는 MFA 폼 */} {!showMfaForm ? ( <> {/* Login Method Tabs */}
{/* Username Login Form */} {loginMethod === 'username' && (
setUsername(e.target.value)} disabled={isLoading} />
setPassword(e.target.value)} disabled={isLoading} />
)} {/* S-Gips Login Form */} {loginMethod === 'sgips' && (
setSgipsUsername(e.target.value)} disabled={isLoading} />
setSgipsPassword(e.target.value)} disabled={isLoading} />

S-Gips 계정으로 로그인하면 자동으로 SMS 인증이 진행됩니다.

)} {/* Additional Links */}
{loginMethod === 'username' && ( )} {/* 테스트용 MFA 화면 버튼 */} {process.env.NODE_ENV === 'development' && ( )}
) : ( /* MFA 입력 폼 */
{/* 뒤로 가기 버튼 */}
{/* SMS 재전송 섹션 */}

인증번호 재전송

인증번호를 받지 못하셨나요?

{/* SMS 토큰 입력 폼 */}
setMfaToken(value)} >
{/* 도움말 */}
⚠️

인증번호를 받지 못하셨나요?

  • 전화번호가 올바른지 확인해주세요
  • 스팸 메시지함을 확인해주세요
  • 잠시 후 재전송 버튼을 이용해주세요
)} {/* 비밀번호 재설정 다이얼로그 */} {showForgotPassword && !showMfaForm && (

비밀번호 재설정

가입하신 이메일 주소를 입력하시면 비밀번호 재설정 링크를 보내드립니다.

)} {/* Language Selector - MFA 화면에서는 숨김 */} {!showMfaForm && (
handleChangeLanguage(value)} > {t('languages.english')} {t('languages.korean')}
)}
{/* Terms - MFA 화면에서는 숨김 */} {!showMfaForm && (
{t("agreement")}{" "} {t("privacyPolicy")}
)}
{/* Right BG 이미지 영역 */}
Background image

“{t("blockquote")}”

) }