diff options
| -rw-r--r-- | components/login/login-form.tsx | 393 | ||||
| -rw-r--r-- | public/background-videos/1.yard.mp4 | bin | 0 -> 4890582 bytes | |||
| -rw-r--r-- | public/background-videos/2.SN2635_LNGC_CARDIFF.mp4 | bin | 0 -> 3161018 bytes | |||
| -rw-r--r-- | public/background-videos/3.SN2628_CONT_WAN HAI.mp4 | bin | 0 -> 2447830 bytes | |||
| -rw-r--r-- | public/background-videos/4.SN2612_LNGC_KGL.mp4 | bin | 0 -> 3070863 bytes | |||
| -rw-r--r-- | public/background-videos/5.SN2596_LNGC_JP MORGAN.mp4 | bin | 0 -> 4426638 bytes | |||
| -rw-r--r-- | public/background-videos/6.2235_FLNG_ENI_CORAL.mp4 | bin | 0 -> 3121327 bytes | |||
| -rw-r--r-- | public/background-videos/7.2126_FLNG_PETRONAS.mp4 | bin | 0 -> 3605598 bytes |
8 files changed, 71 insertions, 322 deletions
diff --git a/components/login/login-form.tsx b/components/login/login-form.tsx index bcfa1ec6..51d54531 100644 --- a/components/login/login-form.tsx +++ b/components/login/login-form.tsx @@ -12,7 +12,6 @@ import { useRouter, useParams, usePathname, useSearchParams } from 'next/navigat import { signIn, useSession } 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, @@ -23,16 +22,16 @@ import { requestPasswordResetAction } from "@/lib/users/auth/partners-auth"; import { checkEmailAndStartAuth, resendEmailOtp } from "@/lib/users/auth/email-auth"; import Loading from "../common/loading/loading"; -type LoginMethod = 'username' | 'sgips'; - -type OtpUser = { - name: string; - vndrcd: string; - phone: string; - email: string; - nation_cd: string; - userId: number; // 백엔드에서 생성된 로컬 DB 사용자 ID -}; +// 배경 영상 목록 +const BACKGROUND_VIDEOS = [ + '/background-videos/1.yard.mp4', + '/background-videos/2.SN2635_LNGC_CARDIFF.mp4', + '/background-videos/3.SN2628_CONT_WAN HAI.mp4', + '/background-videos/4.SN2612_LNGC_KGL.mp4', + '/background-videos/5.SN2596_LNGC_JP MORGAN.mp4', + '/background-videos/6.2235_FLNG_ENI_CORAL.mp4', + '/background-videos/7.2126_FLNG_PETRONAS.mp4', +]; export function LoginForm() { const params = useParams() || {}; @@ -45,8 +44,10 @@ export function LoginForm() { const { toast } = useToast(); const { data: session, status } = useSession(); + // 배경 영상 상태 + const [currentVideoIndex, setCurrentVideoIndex] = useState(0); + // 상태 관리 - const [loginMethod, setLoginMethod] = useState<LoginMethod>('username'); const [isFirstAuthLoading, setIsFirstAuthLoading] = useState(false); const [showForgotPassword, setShowForgotPassword] = useState(false); @@ -69,15 +70,6 @@ export function LoginForm() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); - // S-Gips 로그인 폼 데이터 - const [sgipsUsername, setSgipsUsername] = useState(''); - const [sgipsPassword, setSgipsPassword] = useState(''); - - // OTP 사용자 선택 관련 상태 - const [otpUsers, setOtpUsers] = useState<OtpUser[]>([]); - const [showUserSelectionDialog, setShowUserSelectionDialog] = useState(false); - const [selectedOtpUser, setSelectedOtpUser] = useState<OtpUser | null>(null); - const [isMfaLoading, setIsMfaLoading] = useState(false); const [isSmsLoading, setIsSmsLoading] = useState(false); @@ -91,11 +83,14 @@ export function LoginForm() { // 호스트명 확인 상태 추가 const [isDataRoomHost, setIsDataRoomHost] = useState(false); - // 컴포넌트 마운트 시 호스트명 확인 + // 컴포넌트 마운트 시 호스트명 확인 및 랜덤 비디오 선택 useEffect(() => { if (typeof window !== 'undefined') { const hostname = window.location.hostname; setIsDataRoomHost(hostname.includes('shidataroom')); + + // 랜덤한 비디오 인덱스 선택 + setCurrentVideoIndex(Math.floor(Math.random() * BACKGROUND_VIDEOS.length)); } }, []); @@ -144,7 +139,7 @@ export function LoginForm() { const currentLanguageText = i18n.language === 'ko' ? t('languages.korean') : t('languages.english'); // 세분화된 에러 메시지 처리 함수 - const getErrorMessage = (error: { errorCode?: string; message?: string }, provider: 'email' | 'sgips') => { + const getErrorMessage = (error: { errorCode?: string; message?: string }) => { const errorCode = error.errorCode; if (!errorCode) { @@ -153,7 +148,7 @@ export function LoginForm() { switch (errorCode) { case 'INVALID_CREDENTIALS': - return provider === 'sgips' ? t('sgipsInvalidCredentials') : t('invalidCredentials'); + return t('invalidCredentials'); case 'ACCOUNT_LOCKED': return t('accountLocked'); case 'ACCOUNT_DEACTIVATED': @@ -173,6 +168,11 @@ export function LoginForm() { router.push(`/${lng}/partners/repository`); }; + // 비디오 종료 시 다음 비디오로 전환 + const handleVideoEnd = () => { + setCurrentVideoIndex((prevIndex) => (prevIndex + 1) % BACKGROUND_VIDEOS.length); + }; + // MFA 카운트다운 효과 useEffect(() => { if (mfaCountdown > 0) { @@ -234,13 +234,7 @@ export function LoginForm() { setIsSmsLoading(true); try { - const requestBody: { userId: number; phone?: string; name?: string } = { userId: targetUserId }; - - // S-GIPS 사용자인 경우 추가 정보 포함 - if (selectedOtpUser) { - requestBody.phone = selectedOtpUser.phone; - requestBody.name = selectedOtpUser.name; - } + const requestBody: { userId: number } = { userId: targetUserId }; const response = await fetch('/api/auth/send-sms', { method: 'POST', @@ -250,17 +244,10 @@ export function LoginForm() { if (response.ok) { setMfaCountdown(60); - if (selectedOtpUser) { - toast({ - title: t('smsSent'), - description: t('smsCodeSentTo', { name: selectedOtpUser.name, phone: selectedOtpUser.phone }), - }); - } else { - toast({ - title: t('smsSent'), - description: t('smsCodeSent'), - }); - } + toast({ + title: t('smsSent'), + description: t('smsCodeSent'), + }); } else { const errorData = await response.json(); toast({ @@ -428,8 +415,7 @@ export function LoginForm() { if (!result.success) { const errorMessage = getErrorMessage( - { errorCode: result.errorCode, message: result.error }, - 'email' + { errorCode: result.errorCode, message: result.error } ); toast({ @@ -526,8 +512,7 @@ export function LoginForm() { console.error('Password submit error:', error); const errorMessage = getErrorMessage( - error as { errorCode?: string; message?: string }, - 'email' + error as { errorCode?: string; message?: string } ); toast({ @@ -540,124 +525,6 @@ export function LoginForm() { } }; - // S-Gips 1차 인증 처리 - const handleSgipsLogin = async (e: React.FormEvent) => { - e.preventDefault(); - - if (!sgipsUsername || !sgipsPassword) { - toast({ - title: t('errorTitle'), - description: t('credentialsRequired'), - variant: 'destructive', - }); - return; - } - - setIsFirstAuthLoading(true); - - try { - // S-Gips 1차 인증만 수행 (세션 생성 안함) - const authResult = await performFirstAuth(sgipsUsername, sgipsPassword, 'sgips'); - - if (authResult.success) { - const users = authResult.otpUsers || []; - - if (users.length === 0) { - toast({ - title: t('errorTitle'), - description: t('noUsersFound'), - variant: 'destructive', - }); - return; - } - - // 사용자 선택 다이얼로그 표시 (항상) - setOtpUsers(users); - setShowUserSelectionDialog(true); - } - } catch (error: unknown) { - console.error('S-Gips login error:', error); - - const errorMessage = getErrorMessage(error as { errorCode?: string; message?: string }, 'sgips'); - - toast({ - title: t('errorTitle'), - description: errorMessage, - variant: 'destructive', - }); - } finally { - setIsFirstAuthLoading(false); - } - }; - - // 선택된 OTP 사용자와 함께 MFA 진행 - const proceedWithSelectedUser = async (user: OtpUser, tempAuthKey: string) => { - try { - - // 사용자 정보를 기반으로 MFA 진행 - setTempAuthKey(tempAuthKey); - setSelectedOtpUser(user); - setMfaUserId(user.userId); // 선택된 사용자의 userId 설정 - setMfaUserEmail(user.email); - setShowMfaForm(true); - - // 선택된 사용자의 userId를 직접 전달하여 SMS 전송 - setTimeout(() => { - handleSendSms(user.userId); - }, 2000); - - toast({ - title: t('sgipsAuthComplete'), - description: t('sendingCodeToSelectedUser', { name: user.name }), - }); - } catch (error) { - console.error('Proceeding with selected user error:', error); - toast({ - title: t('errorTitle'), - description: t('mfaSetupError'), - variant: 'destructive', - }); - } - }; - - // OTP 사용자 선택 처리 - const handleUserSelection = async (user: OtpUser) => { - setShowUserSelectionDialog(false); - - try { - // 선택된 사용자에 대한 임시 인증 세션 생성 요청 - const response = await fetch('/api/auth/select-sgips-user', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - userId: user.userId, - email: user.email, - name: user.name - }), - }); - - const result = await response.json(); - - if (!response.ok || !result.success) { - toast({ - title: t('errorTitle'), - description: result.error || t('mfaSetupError'), - variant: 'destructive', - }); - return; - } - - // 임시 인증 세션 생성 성공, MFA 진행 - await proceedWithSelectedUser(user, result.tempAuthKey); - } catch (error) { - console.error('User selection error:', error); - toast({ - title: t('errorTitle'), - description: t('mfaSetupError'), - variant: 'destructive', - }); - } - }; // MFA 화면 또는 패스워드 입력에서 뒤로 가기 const handleBackToLogin = () => { @@ -692,11 +559,6 @@ export function LoginForm() { setPassword(''); setUsername(''); } - - // S-Gips 관련 초기화 - setSelectedOtpUser(null); - setShowUserSelectionDialog(false); - setOtpUsers([]); }; // 세션 로딩 중이거나 이미 인증된 상태에서는 로딩 표시 @@ -758,11 +620,9 @@ export function LoginForm() { {mfaType === 'email' ? t('emailVerification') : t('smsVerification')} </h1> <p className="text-sm text-muted-foreground mt-2"> - {selectedOtpUser - ? t('firstAuthCompleteForSgips', { name: selectedOtpUser.name, email: mfaUserEmail }) - : mfaType === 'email' - ? t('emailOtpSentTo', { email: mfaUserEmail }) - : t('firstAuthCompleteFor', { email: mfaUserEmail }) + {mfaType === 'email' + ? t('emailOtpSentTo', { email: mfaUserEmail }) + : t('firstAuthCompleteFor', { email: mfaUserEmail }) } </p> <p className="text-xs text-muted-foreground mt-1"> @@ -777,42 +637,10 @@ export function LoginForm() { {/* 로그인 폼 또는 MFA 폼 */} {!showMfaForm ? ( <> - {/* Login Method Tabs */} - <div className="flex rounded-lg bg-muted p-1"> - <button - type="button" - onClick={() => setLoginMethod('username')} - className={cn( - "flex-1 rounded-md px-3 py-2 text-sm font-medium transition-all", - loginMethod === 'username' - ? "bg-background text-foreground shadow-sm" - : "text-muted-foreground hover:text-foreground" - )} - > - {t('generalLogin')} - </button> - {/* S-Gips 로그인은 영문 페이지에서 비활성화 0925 구매 요청사항*/} - {lng !== 'en' && ( - <button - type="button" - onClick={() => setLoginMethod('sgips')} - className={cn( - "flex-1 rounded-md px-3 py-2 text-sm font-medium transition-all", - loginMethod === 'sgips' - ? "bg-background text-foreground shadow-sm" - : "text-muted-foreground hover:text-foreground" - )} - > - {t('sgipsLogin')} - </button> - )} - </div> - - {/* Username Login Form */} - {loginMethod === 'username' && ( - <> - {!showPasswordInput ? ( - // 1단계: 이메일만 입력 + {/* Login Form */} + <> + {!showPasswordInput ? ( + // 1단계: 이메일만 입력 <form onSubmit={handleEmailSubmit} className="grid gap-4"> <div className="grid gap-2"> <Input @@ -885,49 +713,7 @@ export function LoginForm() { </form> </div> )} - </> - )} - - {/* S-Gips Login Form - 영문 페이지에서 비활성화 0925 구매 요청사항*/} - {loginMethod === 'sgips' && lng !== 'en' && ( - <form onSubmit={handleSgipsLogin} className="grid gap-4"> - <div className="grid gap-2"> - <Input - id="sgipsUsername" - type="text" - placeholder={t('sgipsId')} - required - className="h-10" - value={sgipsUsername} - onChange={(e) => setSgipsUsername(e.target.value)} - disabled={isFirstAuthLoading} - /> - </div> - <div className="grid gap-2"> - <Input - id="sgipsPassword" - type="password" - placeholder={t('sgipsPassword')} - required - className="h-10" - value={sgipsPassword} - onChange={(e) => setSgipsPassword(e.target.value)} - disabled={isFirstAuthLoading} - /> - </div> - <Button - type="submit" - className="w-full" - variant="default" - disabled={isFirstAuthLoading || !sgipsUsername || !sgipsPassword} - > - {isFirstAuthLoading ? t('sgipsAuthenticating') : t('sgipsLogin')} - </Button> - <p className="text-xs text-muted-foreground text-center"> - {t('sgipsAutoSms')} - </p> - </form> - )} + </> {/* Additional Links */} <div className="flex flex-col gap-2 text-center"> @@ -944,7 +730,7 @@ export function LoginForm() { )} {/* 비밀번호 찾기는 패스워드 입력 단계에서만 표시 */} - {loginMethod === 'username' && showPasswordInput && ( + {showPasswordInput && ( <Button type="button" variant="link" @@ -1066,62 +852,6 @@ export function LoginForm() { </div> )} - {/* OTP 사용자 선택 다이얼로그 */} - {showUserSelectionDialog && !showMfaForm && ( - <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> - <div className="bg-white rounded-lg p-6 w-full max-w-md mx-4 max-h-[80vh] overflow-y-auto"> - <div className="flex justify-between items-center mb-4"> - <h3 className="text-lg font-semibold">{t('selectUser')}</h3> - <button - onClick={() => setShowUserSelectionDialog(false)} - className="text-gray-400 hover:text-gray-600" - > - ✕ - </button> - </div> - <div className="space-y-3"> - <p className="text-sm text-gray-600 mb-4"> - {t('selectUserDescription')} - </p> - {otpUsers.map((user, index) => ( - <div - key={index} - className="border rounded-lg p-4 hover:bg-gray-50 cursor-pointer" - onClick={() => handleUserSelection(user)} - > - <div className="flex justify-between items-start"> - <div className="flex-1"> - <div className="font-medium text-gray-900">{user.name}</div> - <div className="text-sm text-gray-600">{user.email}</div> - <div className="text-sm text-gray-500">{user.phone}</div> - <div className="text-xs text-gray-400 mt-1">Vendor: {user.vndrcd}</div> - </div> - <Button - size="sm" - variant="outline" - onClick={(e) => { - e.stopPropagation(); - handleUserSelection(user); - }} - > - {t('select')} - </Button> - </div> - </div> - ))} - </div> - <div className="flex justify-end mt-6"> - <Button - variant="outline" - onClick={() => setShowUserSelectionDialog(false)} - > - {t('cancel')} - </Button> - </div> - </div> - </div> - )} - {/* 비밀번호 재설정 다이얼로그 */} {showForgotPassword && !showMfaForm && ( <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> @@ -1218,23 +948,42 @@ export function LoginForm() { </div> </div> - {/* Right BG 이미지 영역 */} - <div className="relative hidden h-full flex-col p-10 text-white dark:border-r md:flex"> + {/* Right BG 영상 영역 */} + <div className="relative hidden h-full flex-col p-10 text-white dark:border-r md:flex overflow-hidden"> <div className="absolute inset-0"> - <Image - src="/images/02.jpg" - alt="Background image" - fill - priority - sizes="(max-width: 1024px) 100vw, 50vw" - className="object-cover" - /> + <video + key={currentVideoIndex} + autoPlay + muted + onEnded={handleVideoEnd} + className="w-full h-full object-cover" + playsInline + > + <source src={BACKGROUND_VIDEOS[currentVideoIndex]} type="video/mp4" /> + </video> + {/* 어두운 오버레이 */} + <div className="absolute inset-0 bg-black/30"></div> </div> <div className="relative z-10 mt-auto"> - <blockquote className="space-y-2"> - <p className="text-sm">“{t("blockquote")}”</p> + <blockquote className="space-y-2 backdrop-blur-sm bg-black/20 p-4 rounded-lg"> + <p className="text-sm font-medium drop-shadow-lg">“{t("blockquote")}”</p> </blockquote> </div> + {/* 비디오 인디케이터 */} + <div className="relative z-10 flex justify-center space-x-2 mb-4"> + {BACKGROUND_VIDEOS.map((_, index) => ( + <div + key={index} + className={cn( + "w-2 h-2 rounded-full transition-all duration-300", + index === currentVideoIndex + ? "bg-white w-8" + : "bg-white/50 hover:bg-white/75 cursor-pointer" + )} + onClick={() => setCurrentVideoIndex(index)} + /> + ))} + </div> </div> </div> ) diff --git a/public/background-videos/1.yard.mp4 b/public/background-videos/1.yard.mp4 Binary files differnew file mode 100644 index 00000000..3edf7f8d --- /dev/null +++ b/public/background-videos/1.yard.mp4 diff --git a/public/background-videos/2.SN2635_LNGC_CARDIFF.mp4 b/public/background-videos/2.SN2635_LNGC_CARDIFF.mp4 Binary files differnew file mode 100644 index 00000000..2a061a2a --- /dev/null +++ b/public/background-videos/2.SN2635_LNGC_CARDIFF.mp4 diff --git a/public/background-videos/3.SN2628_CONT_WAN HAI.mp4 b/public/background-videos/3.SN2628_CONT_WAN HAI.mp4 Binary files differnew file mode 100644 index 00000000..75ecdb61 --- /dev/null +++ b/public/background-videos/3.SN2628_CONT_WAN HAI.mp4 diff --git a/public/background-videos/4.SN2612_LNGC_KGL.mp4 b/public/background-videos/4.SN2612_LNGC_KGL.mp4 Binary files differnew file mode 100644 index 00000000..4fbf0a40 --- /dev/null +++ b/public/background-videos/4.SN2612_LNGC_KGL.mp4 diff --git a/public/background-videos/5.SN2596_LNGC_JP MORGAN.mp4 b/public/background-videos/5.SN2596_LNGC_JP MORGAN.mp4 Binary files differnew file mode 100644 index 00000000..044d420f --- /dev/null +++ b/public/background-videos/5.SN2596_LNGC_JP MORGAN.mp4 diff --git a/public/background-videos/6.2235_FLNG_ENI_CORAL.mp4 b/public/background-videos/6.2235_FLNG_ENI_CORAL.mp4 Binary files differnew file mode 100644 index 00000000..65f813be --- /dev/null +++ b/public/background-videos/6.2235_FLNG_ENI_CORAL.mp4 diff --git a/public/background-videos/7.2126_FLNG_PETRONAS.mp4 b/public/background-videos/7.2126_FLNG_PETRONAS.mp4 Binary files differnew file mode 100644 index 00000000..f5bf0d2c --- /dev/null +++ b/public/background-videos/7.2126_FLNG_PETRONAS.mp4 |
