summaryrefslogtreecommitdiff
path: root/components/login
diff options
context:
space:
mode:
Diffstat (limited to 'components/login')
-rw-r--r--components/login/login-form.tsx187
1 files changed, 75 insertions, 112 deletions
diff --git a/components/login/login-form.tsx b/components/login/login-form.tsx
index 51d54531..fae5b8c9 100644
--- a/components/login/login-form.tsx
+++ b/components/login/login-form.tsx
@@ -4,7 +4,7 @@ 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 { 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'
@@ -21,6 +21,7 @@ import {
import { requestPasswordResetAction } from "@/lib/users/auth/partners-auth";
import { checkEmailAndStartAuth, resendEmailOtp } from "@/lib/users/auth/email-auth";
import Loading from "../common/loading/loading";
+import { VideoBackground } from "@/components/common/video-background";
// 배경 영상 목록
const BACKGROUND_VIDEOS = [
@@ -44,9 +45,6 @@ export function LoginForm() {
const { toast } = useToast();
const { data: session, status } = useSession();
- // 배경 영상 상태
- const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
-
// 상태 관리
const [isFirstAuthLoading, setIsFirstAuthLoading] = useState(false);
const [showForgotPassword, setShowForgotPassword] = useState(false);
@@ -83,14 +81,11 @@ 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));
}
}, []);
@@ -168,11 +163,6 @@ export function LoginForm() {
router.push(`/${lng}/partners/repository`);
};
- // 비디오 종료 시 다음 비디오로 전환
- const handleVideoEnd = () => {
- setCurrentVideoIndex((prevIndex) => (prevIndex + 1) % BACKGROUND_VIDEOS.length);
- };
-
// MFA 카운트다운 효과
useEffect(() => {
if (mfaCountdown > 0) {
@@ -576,47 +566,59 @@ export function LoginForm() {
}
return (
- <div className="container relative flex h-screen flex-col items-center justify-center md:grid lg:max-w-none lg:grid-cols-2 lg:px-0">
- {/* Left Content */}
- <div className="flex flex-col w-full h-screen lg:p-2">
- {/* Top bar with Logo + eVCP (left) and "Request Vendor Repository" (right) */}
- <div className="flex items-center justify-between">
- <div className="flex items-center space-x-2">
- <Ship className="w-4 h-4" />
- <span className="text-md font-bold">{isDataRoomHost ? "Data Room" : "eVCP"}</span>
+ <div className="relative flex h-screen w-full items-center justify-start overflow-hidden">
+ {/* 전체 화면 배경 영상 */}
+ <VideoBackground
+ videos={BACKGROUND_VIDEOS}
+ overlayClassName="bg-black/50"
+ showCacheStatus={process.env.NODE_ENV === 'development'}
+ />
+
+ {/* 로그인 카드 */}
+ <div className="relative z-20 w-full max-w-md ml-8 md:ml-16 lg:ml-24">
+ {/* 불투명한 카드 배경 */}
+ <div className="bg-card rounded-lg shadow-2xl overflow-hidden border border-border">
+ {/* 헤더 */}
+ <div className="px-6 py-4 border-b bg-gradient-to-r from-blue-600 to-blue-700">
+ <div className="flex items-center justify-between text-white">
+ <div className="flex items-center space-x-2">
+ {/* <Ship className="w-5 h-5" /> */}
+ <span className="text-lg font-bold">{isDataRoomHost ? "Data Room" : "eVCP"}</span>
+ </div>
+ {!isDataRoomHost && (
+ <Link
+ href="/partners/repository"
+ className={cn(
+ buttonVariants({ variant: "ghost" }),
+ "text-white hover:bg-white/20 hover:text-white"
+ )}
+ >
+ <InfoIcon className="w-4 h-4 mr-1" />
+ {t('registerVendor')}
+ </Link>
+ )}
+ </div>
</div>
- {!isDataRoomHost && (
- <Link
- href="/partners/repository"
- className={cn(buttonVariants({ variant: "ghost" }))}
- >
- <InfoIcon className="w-4 h-4 mr-1" />
- {t('registerVendor')}
- </Link>
- )}
- </div>
- {/* Content section that occupies remaining space, centered vertically */}
- <div className="flex-1 flex items-center justify-center">
- <div className="mx-auto w-full flex flex-col space-y-6 sm:w-[350px]">
- <div className="p-6 md:p-8">
- <div className="flex flex-col gap-6">
- {/* Header */}
- <div className="flex flex-col items-center text-center">
+ {/* 로그인 폼 컨텐츠 */}
+ <div className="p-8">
+ <div className="flex flex-col gap-6">
+ {/* Header */}
+ <div className="flex flex-col items-center text-center">
{!showMfaForm ? (
<>
- <h1 className="text-2xl font-bold">{isDataRoomHost ? t('loginMessageDataRoom') :t('loginMessage')}</h1>
+ <h1 className="text-2xl font-bold text-foreground">{isDataRoomHost ? t('loginMessageDataRoom') :t('loginMessage')}</h1>
<p className="text-xs text-muted-foreground mt-2">
{isDataRoomHost?t('loginDescriptionDataRoom') :t('loginDescription')}
</p>
</>
) : (
<>
- <div className="flex items-center justify-center w-12 h-12 rounded-full bg-blue-100 mb-4">
+ <div className="flex items-center justify-center w-12 h-12 rounded-full bg-blue-100 dark:bg-blue-900/30 mb-4">
{mfaType === 'email' ? '📧' : '🔐'}
</div>
- <h1 className="text-2xl font-bold">
+ <h1 className="text-2xl font-bold text-foreground">
{mfaType === 'email' ? t('emailVerification') : t('smsVerification')}
</h1>
<p className="text-sm text-muted-foreground mt-2">
@@ -674,7 +676,7 @@ export function LoginForm() {
variant="ghost"
size="sm"
onClick={handleBackToLogin}
- className="text-blue-600 hover:text-blue-800"
+ className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
>
<ArrowLeft className="w-4 h-4 mr-1" />
{t('backToLogin')}
@@ -682,9 +684,9 @@ export function LoginForm() {
</div>
{/* 이메일 표시 */}
- <div className="bg-gray-50 p-3 rounded-lg">
- <p className="text-sm text-gray-600 mb-1">{t('loginEmail')}</p>
- <p className="text-sm font-medium text-gray-900">{emailInput}</p>
+ <div className="bg-muted p-3 rounded-lg">
+ <p className="text-sm text-muted-foreground mb-1">{t('loginEmail')}</p>
+ <p className="text-sm font-medium text-foreground">{emailInput}</p>
</div>
{/* 패스워드 입력 폼 */}
@@ -722,7 +724,7 @@ export function LoginForm() {
<Button
type="button"
variant="link"
- className="text-blue-600 hover:text-blue-800 text-sm"
+ className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm"
onClick={goToVendorRegistration}
>
{t('newVendor')}
@@ -734,7 +736,7 @@ export function LoginForm() {
<Button
type="button"
variant="link"
- className="text-blue-600 hover:text-blue-800 text-sm"
+ className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm"
onClick={() => setShowForgotPassword(true)}
>
{t('forgotPassword')}
@@ -752,7 +754,7 @@ export function LoginForm() {
variant="ghost"
size="sm"
onClick={handleBackToLogin}
- className="text-blue-600 hover:text-blue-800"
+ className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
>
<ArrowLeft className="w-4 h-4 mr-1" />
{t('backToLogin')}
@@ -760,11 +762,11 @@ export function LoginForm() {
</div>
{/* OTP 재전송 섹션 (SMS/Email) */}
- <div className="bg-gray-50 p-4 rounded-lg">
- <h3 className="text-sm font-medium text-gray-900 mb-2">
+ <div className="bg-muted p-4 rounded-lg">
+ <h3 className="text-sm font-medium text-foreground mb-2">
{t('resendCode')}
</h3>
- <p className="text-xs text-gray-600 mb-3">
+ <p className="text-xs text-muted-foreground mb-3">
{mfaType === 'email'
? t('didNotReceiveEmail')
: t('didNotReceiveCode')}
@@ -797,7 +799,7 @@ export function LoginForm() {
<form onSubmit={handleMfaSubmit} className="space-y-6">
<div className="space-y-4">
<div className="text-center">
- <label className="block text-sm font-medium text-gray-700 mb-3">
+ <label className="block text-sm font-medium text-foreground mb-3">
{t('enterSixDigitCode')}
</label>
<div className="flex justify-center">
@@ -830,16 +832,16 @@ export function LoginForm() {
</form>
{/* 도움말 */}
- <div className="bg-yellow-50 p-3 rounded-lg border border-yellow-200">
+ <div className="bg-yellow-50 dark:bg-yellow-900/20 p-3 rounded-lg border border-yellow-200 dark:border-yellow-800">
<div className="flex">
<div className="flex-shrink-0">
⚠️
</div>
<div className="ml-2">
- <h4 className="text-xs font-medium text-yellow-800">
+ <h4 className="text-xs font-medium text-yellow-800 dark:text-yellow-300">
{t('didNotReceiveCode')}
</h4>
- <div className="mt-1 text-xs text-yellow-700">
+ <div className="mt-1 text-xs text-yellow-700 dark:text-yellow-400">
<ul className="list-disc list-inside space-y-1">
<li>{t('checkPhoneNumber')}</li>
<li>{t('checkSpamFolder')}</li>
@@ -854,22 +856,22 @@ export function LoginForm() {
{/* 비밀번호 재설정 다이얼로그 */}
{showForgotPassword && !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">
+ <div className="fixed inset-0 bg-black/50 dark:bg-black/70 flex items-center justify-center z-50">
+ <div className="bg-card rounded-lg p-6 w-full max-w-md mx-4 border border-border">
<div className="flex justify-between items-center mb-4">
- <h3 className="text-lg font-semibold">{t('resetPassword')}</h3>
+ <h3 className="text-lg font-semibold text-foreground">{t('resetPassword')}</h3>
<button
onClick={() => {
setShowForgotPassword(false);
}}
- className="text-gray-400 hover:text-gray-600"
+ className="text-muted-foreground hover:text-foreground"
>
</button>
</div>
<form action={passwordResetAction} className="space-y-4">
<div>
- <p className="text-sm text-gray-600 mb-3">
+ <p className="text-sm text-muted-foreground mb-3">
{t('resetDescription')}
</p>
<Input
@@ -929,60 +931,21 @@ export function LoginForm() {
</DropdownMenu>
</div>
)}
- </div>
- </div>
- {/* Terms - MFA 화면에서는 숨김 */}
- {!showMfaForm && (
- <div className="text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 hover:[&_a]:text-primary">
- {t("agreement")}{" "}
- <Link
- href={`/${lng}/privacy`}
- className="underline underline-offset-4 hover:text-primary"
- >
- {t("privacyPolicy")}
- </Link>
- </div>
- )}
- </div>
- </div>
- </div>
-
- {/* 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">
- <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 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"
+ {/* Terms - MFA 화면에서는 숨김 */}
+ {!showMfaForm && (
+ <div className="text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 hover:[&_a]:text-primary mt-4">
+ {t("agreement")}{" "}
+ <Link
+ href={`/${lng}/privacy`}
+ className="underline underline-offset-4 hover:text-primary"
+ >
+ {t("privacyPolicy")}
+ </Link>
+ </div>
)}
- onClick={() => setCurrentVideoIndex(index)}
- />
- ))}
+ </div>
+ </div>
</div>
</div>
</div>