summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-02 15:06:16 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-02 15:06:16 +0900
commitbed369de9e206d56489c5691560909fb133d5624 (patch)
tree280fe8742d2a4b5f343b7b4cea1f12ed0c2aa5a0
parentba30be6035f1a5e3afc4d1d8b1e346c4db18537a (diff)
(김준회) login-form 리팩터링 (S-GIPS 제거) 및 배경 영상으로 변경
-rw-r--r--components/login/login-form.tsx393
-rw-r--r--public/background-videos/1.yard.mp4bin0 -> 4890582 bytes
-rw-r--r--public/background-videos/2.SN2635_LNGC_CARDIFF.mp4bin0 -> 3161018 bytes
-rw-r--r--public/background-videos/3.SN2628_CONT_WAN HAI.mp4bin0 -> 2447830 bytes
-rw-r--r--public/background-videos/4.SN2612_LNGC_KGL.mp4bin0 -> 3070863 bytes
-rw-r--r--public/background-videos/5.SN2596_LNGC_JP MORGAN.mp4bin0 -> 4426638 bytes
-rw-r--r--public/background-videos/6.2235_FLNG_ENI_CORAL.mp4bin0 -> 3121327 bytes
-rw-r--r--public/background-videos/7.2126_FLNG_PETRONAS.mp4bin0 -> 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">&ldquo;{t("blockquote")}&rdquo;</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">&ldquo;{t("blockquote")}&rdquo;</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
new file mode 100644
index 00000000..3edf7f8d
--- /dev/null
+++ b/public/background-videos/1.yard.mp4
Binary files differ
diff --git a/public/background-videos/2.SN2635_LNGC_CARDIFF.mp4 b/public/background-videos/2.SN2635_LNGC_CARDIFF.mp4
new file mode 100644
index 00000000..2a061a2a
--- /dev/null
+++ b/public/background-videos/2.SN2635_LNGC_CARDIFF.mp4
Binary files differ
diff --git a/public/background-videos/3.SN2628_CONT_WAN HAI.mp4 b/public/background-videos/3.SN2628_CONT_WAN HAI.mp4
new file mode 100644
index 00000000..75ecdb61
--- /dev/null
+++ b/public/background-videos/3.SN2628_CONT_WAN HAI.mp4
Binary files differ
diff --git a/public/background-videos/4.SN2612_LNGC_KGL.mp4 b/public/background-videos/4.SN2612_LNGC_KGL.mp4
new file mode 100644
index 00000000..4fbf0a40
--- /dev/null
+++ b/public/background-videos/4.SN2612_LNGC_KGL.mp4
Binary files differ
diff --git a/public/background-videos/5.SN2596_LNGC_JP MORGAN.mp4 b/public/background-videos/5.SN2596_LNGC_JP MORGAN.mp4
new file mode 100644
index 00000000..044d420f
--- /dev/null
+++ b/public/background-videos/5.SN2596_LNGC_JP MORGAN.mp4
Binary files differ
diff --git a/public/background-videos/6.2235_FLNG_ENI_CORAL.mp4 b/public/background-videos/6.2235_FLNG_ENI_CORAL.mp4
new file mode 100644
index 00000000..65f813be
--- /dev/null
+++ b/public/background-videos/6.2235_FLNG_ENI_CORAL.mp4
Binary files differ
diff --git a/public/background-videos/7.2126_FLNG_PETRONAS.mp4 b/public/background-videos/7.2126_FLNG_PETRONAS.mp4
new file mode 100644
index 00000000..f5bf0d2c
--- /dev/null
+++ b/public/background-videos/7.2126_FLNG_PETRONAS.mp4
Binary files differ