From 9ceed79cf32c896f8a998399bf1b296506b2cd4a Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 8 Apr 2025 03:08:19 +0000 Subject: 로그인 및 미들웨어 처리. 구조 변경 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/login/login-form-shi.tsx | 318 ++++++++++++++++++++++++++++++++++++ components/login/login-form.tsx | 201 ++++++++++++++++++----- 2 files changed, 480 insertions(+), 39 deletions(-) create mode 100644 components/login/login-form-shi.tsx (limited to 'components/login') diff --git a/components/login/login-form-shi.tsx b/components/login/login-form-shi.tsx new file mode 100644 index 00000000..fb985592 --- /dev/null +++ b/components/login/login-form-shi.tsx @@ -0,0 +1,318 @@ +'use client'; + +import { useState, useEffect } from "react"; +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Card, CardContent } from "@/components/ui/card" +import { Input } from "@/components/ui/input" +import { SendIcon, Loader2, GlobeIcon, ChevronDownIcon, Ship } 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 { + InputOTP, + InputOTPGroup, + InputOTPSlot, +} from "@/components/ui/input-otp" +import { signIn } from 'next-auth/react'; +import { sendOtpAction } from "@/lib/users/send-otp"; +import { verifyTokenAction } from "@/lib/users/verifyToken"; +import { buttonVariants } from "@/components/ui/button" +import Link from "next/link" +import Image from 'next/image'; // 추가: Image 컴포넌트 import + +export function LoginFormSHI({ + className, + ...props +}: React.ComponentProps<"div">) { + + const params = useParams() || {}; + const pathname = usePathname() || ''; + const router = useRouter(); + const searchParams = useSearchParams(); + const token = searchParams?.get('token') || null; + + + const lng = params.lng as string; + const { t, i18n } = useTranslation(lng, 'login'); + + const { toast } = useToast(); + + 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 [email, setEmail] = useState(''); + const [otpSent, setOtpSent] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [otp, setOtp] = useState(''); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsLoading(true); + try { + const result = await sendOtpAction(email, lng); + + if (result.success) { + setOtpSent(true); + toast({ + title: t('otpSentTitle'), + description: t('otpSentMessage'), + }); + } else { + // Handle specific error types + let errorMessage = t('defaultErrorMessage'); + + // You can handle different error types differently + if (result.error === 'userNotFound') { + errorMessage = t('userNotFoundMessage'); + } + + toast({ + title: t('errorTitle'), + description: result.message || errorMessage, + variant: 'destructive', + }); + } + } catch (error) { + // This will catch network errors or other unexpected issues + console.error(error); + toast({ + title: t('errorTitle'), + description: t('networkErrorMessage'), + variant: 'destructive', + }); + } finally { + setIsLoading(false); + } + }; + + async function handleOtpSubmit(e: React.FormEvent) { + e.preventDefault(); + setIsLoading(true); + + try { + // next-auth의 Credentials Provider로 로그인 시도 + const result = await signIn('credentials', { + email, + code: otp, + redirect: false, // 커스텀 처리 위해 redirect: false + }); + + if (result?.ok) { + // 토스트 메시지 표시 + toast({ + title: t('loginSuccess'), + description: t('youAreLoggedIn'), + }); + + router.push(`/${lng}/evcp/report`); + + } else { + toast({ + title: t('errorTitle'), + description: t('defaultErrorMessage'), + variant: 'destructive', + }); + } + } catch (error) { + console.error('Login error:', error); + toast({ + title: t('errorTitle'), + description: t('defaultErrorMessage'), + variant: 'destructive', + }); + } finally { + setIsLoading(false); + } + } + + useEffect(() => { + const verifyToken = async () => { + if (!token) return; + setIsLoading(true); + + try { + const data = await verifyTokenAction(token); + + if (data.valid) { + setOtpSent(true); + setEmail(data.email ?? ''); + } else { + toast({ + title: t('errorTitle'), + description: t('invalidToken'), + variant: 'destructive', + }); + } + } catch (error) { + toast({ + title: t('errorTitle'), + description: t('defaultErrorMessage'), + variant: 'destructive', + }); + } finally { + setIsLoading(false); + } + }; + verifyToken(); + }, [token, toast, t]); + + return ( +
+ {/* Left Content */} +
+ {/* Top bar with Logo + eVCP (left) and "Request Vendor Repository" (right) */} +
+
+ {/* logo */} + + eVCP +
+
+ + {/* Content section that occupies remaining space, centered vertically */} +
+ {/* Your form container */} +
+ + {/* Here's your existing login/OTP forms: */} + {!otpSent ? ( +
+
+
+

{t('loginMessage')}

+
+
+ setEmail(e.target.value)} + /> +
+ +
+ + + + + + handleChangeLanguage(value)} + > + + {t('languages.english')} + + + {t('languages.korean')} + + + + +
+
+
+ ) : ( +
+
+
+

{t('loginMessage')}

+
+
+ setOtp(value)} + > + + + + + + + + + +
+ +
+ + + + + + handleChangeLanguage(value)} + > + + {t('languages.english')} + + + {t('languages.korean')} + + + + +
+
+
+ )} + +
+ {t('termsMessage')} {t('termsOfService')} {t('and')} + {t('privacyPolicy')}. +
+
+
+
+ + {/* Right BG 이미지 영역 - Image 컴포넌트로 수정 */} +
+ {/* Image 컴포넌트로 대체 */} +
+ Background image +
+
+
+

“{t("blockquote")}”

+ {/*
SHI
*/} +
+
+
+
+ ) +} \ No newline at end of file diff --git a/components/login/login-form.tsx b/components/login/login-form.tsx index 41d232f8..92fa6e2c 100644 --- a/components/login/login-form.tsx +++ b/components/login/login-form.tsx @@ -32,7 +32,8 @@ export function LoginForm({ const router = useRouter(); const searchParams = useSearchParams(); const token = searchParams?.get('token') || null; - + const [showCredentialsForm, setShowCredentialsForm] = useState(false); + const lng = params.lng as string; const { t, i18n } = useTranslation(lng, 'login'); @@ -51,6 +52,8 @@ export function LoginForm({ const [otpSent, setOtpSent] = useState(false); const [isLoading, setIsLoading] = useState(false); const [otp, setOtp] = useState(''); + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -58,17 +61,33 @@ export function LoginForm({ try { const result = await sendOtpAction(email, lng); - if (result?.success) { + if (result.success) { setOtpSent(true); toast({ title: t('otpSentTitle'), description: t('otpSentMessage'), }); + } else { + // Handle specific error types + let errorMessage = t('defaultErrorMessage'); + + // You can handle different error types differently + if (result.error === 'userNotFound') { + errorMessage = t('userNotFoundMessage'); + } + + toast({ + title: t('errorTitle'), + description: result.message || errorMessage, + variant: 'destructive', + }); } } catch (error) { + // This will catch network errors or other unexpected issues + console.error(error); toast({ title: t('errorTitle'), - description: t('defaultErrorMessage'), + description: t('networkErrorMessage'), variant: 'destructive', }); } finally { @@ -76,11 +95,10 @@ export function LoginForm({ } }; - async function handleOtpSubmit(e: React.FormEvent) { e.preventDefault(); setIsLoading(true); - + try { // next-auth의 Credentials Provider로 로그인 시도 const result = await signIn('credentials', { @@ -88,30 +106,16 @@ export function LoginForm({ code: otp, redirect: false, // 커스텀 처리 위해 redirect: false }); - + if (result?.ok) { // 토스트 메시지 표시 toast({ title: t('loginSuccess'), description: t('youAreLoggedIn'), }); - - // NextAuth에서 유저 정보 API 호출 (최신 상태 보장) - const response = await fetch('/api/auth/session'); - const session = await response.json(); - - // domain 값에 따라 동적으로 리다이렉션 - const userDomain = session?.user?.domain; - console.log(session) - - if (userDomain === 'evcp') { - router.push(`/${lng}/evcp/report`); - } else if (userDomain === 'partners') { - router.push(`/${lng}/partners/dashboard`); - } else { - // 기본 리다이렉션 경로 - router.push(`/${lng}/dashboard`); - } + + router.push(`/${lng}/partners/dashboard`); + } else { toast({ title: t('errorTitle'), @@ -131,6 +135,53 @@ export function LoginForm({ } } + // 새로운 로그인 처리 함수 추가 + const handleCredentialsLogin = async () => { + if (!username || !password) { + toast({ + title: t('errorTitle'), + description: t('credentialsRequired'), + variant: 'destructive', + }); + return; + } + + setIsLoading(true); + + try { + // next-auth의 다른 credentials provider로 로그인 시도 + const result = await signIn('credentials-password', { + username, + password, + redirect: false, + }); + + if (result?.ok) { + toast({ + title: t('loginSuccess'), + description: t('youAreLoggedIn'), + }); + + router.push(`/${lng}/partners/dashboard`); + } else { + toast({ + title: t('errorTitle'), + description: t('invalidCredentials'), + variant: 'destructive', + }); + } + } catch (error) { + console.error('Login error:', error); + toast({ + title: t('errorTitle'), + description: t('defaultErrorMessage'), + variant: 'destructive', + }); + } finally { + setIsLoading(false); + } + }; + useEffect(() => { const verifyToken = async () => { if (!token) return; @@ -197,20 +248,92 @@ export function LoginForm({

{t('loginMessage')}

-
- setEmail(e.target.value)} - /> -
- + + {/* S-chips 로그인 폼이 표시되지 않을 때만 이메일 입력 필드 표시 */} + {!showCredentialsForm && ( + <> +
+ setEmail(e.target.value)} + /> +
+ + + {/* 구분선과 "Or continue with" 섹션 추가 */} +
+
+ +
+
+ + {t('orContinueWith')} + +
+
+ + {/* S-chips 로그인 버튼 */} + + + )} + + {/* ID/비밀번호 로그인 폼 - 버튼 클릭 시에만 표시 */} + {showCredentialsForm && ( + <> +
+ setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + + + {/* 뒤로 가기 버튼 */} + +
+ + )} +
@@ -302,8 +425,8 @@ export function LoginForm({
{/* Image 컴포넌트로 대체 */}
-