summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-03 17:21:09 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-03 17:21:09 +0900
commitdf5a6201bdf8ff9edfc6429b918cf2fd2b245684 (patch)
tree2564a861f69b8803a29db7786bcf1113557ddd0f /components
parent6653a4bdf9ac9d12037ac62cfe8c8d31d60cadd3 (diff)
(김준회) Revert: 로그인 영상 관련 커밋 revert 처리
Diffstat (limited to 'components')
-rw-r--r--components/common/video-background.tsx181
-rw-r--r--components/login/login-form.tsx187
2 files changed, 112 insertions, 256 deletions
diff --git a/components/common/video-background.tsx b/components/common/video-background.tsx
deleted file mode 100644
index 96b9b4ac..00000000
--- a/components/common/video-background.tsx
+++ /dev/null
@@ -1,181 +0,0 @@
-'use client';
-
-import { useState, useEffect, useRef } from 'react';
-import { cn } from '@/lib/utils';
-import { registerServiceWorker, getCacheStatus } from '@/lib/service-worker/register';
-
-interface VideoBackgroundProps {
- videos: string[];
- className?: string;
- overlayClassName?: string;
- showIndicators?: boolean;
- showCacheStatus?: boolean;
- onVideoChange?: (index: number) => void;
-}
-
-/**
- * VideoBackground 컴포넌트
- *
- * 특징:
- * - 영상 프리로드로 부드러운 전환
- * - 자동 재생 및 순환
- * - Service Worker를 통한 영구 캐싱
- * - HTTP 캐시 헤더 지원
- * - 인디케이터 지원
- */
-export function VideoBackground({
- videos,
- className,
- overlayClassName,
- showIndicators = true,
- showCacheStatus = false,
- onVideoChange,
-}: VideoBackgroundProps) {
- const [currentVideoIndex, setCurrentVideoIndex] = useState(0);
- const [isVideoLoaded, setIsVideoLoaded] = useState(false);
- const [cachedVideos, setCachedVideos] = useState(0);
- const videoRef = useRef<HTMLVideoElement>(null);
- const preloadedVideos = useRef<HTMLVideoElement[]>([]);
-
- // Service Worker 등록
- useEffect(() => {
- const setupServiceWorker = async () => {
- const registration = await registerServiceWorker();
-
- if (registration) {
- console.log('✅ Service Worker registered - Videos will be cached');
-
- // 캐시 상태 확인
- setTimeout(async () => {
- const status = await getCacheStatus();
- if (status) {
- setCachedVideos(status.cached);
- console.log(`📦 Cached videos: ${status.cached}/${status.total}`);
- }
- }, 2000);
- }
- };
-
- setupServiceWorker();
- }, []);
-
- // 초기 랜덤 비디오 선택
- useEffect(() => {
- const randomIndex = Math.floor(Math.random() * videos.length);
- setCurrentVideoIndex(randomIndex);
- }, [videos.length]);
-
- // 모든 비디오 프리로드
- useEffect(() => {
- // 이미 프리로드된 경우 스킵
- if (preloadedVideos.current.length > 0) return;
-
- const preloadVideos = async () => {
- const loadedVideos: HTMLVideoElement[] = [];
-
- for (let i = 0; i < videos.length; i++) {
- const video = document.createElement('video');
- video.preload = 'auto';
- video.muted = true;
- video.playsInline = true;
- video.src = videos[i];
-
- // 메타데이터 로드 대기
- await new Promise<void>((resolve) => {
- video.addEventListener('loadedmetadata', () => resolve(), { once: true });
- // 타임아웃 설정 (10초)
- setTimeout(() => resolve(), 10000);
- });
-
- loadedVideos.push(video);
- }
-
- preloadedVideos.current = loadedVideos;
- setIsVideoLoaded(true);
- };
-
- preloadVideos();
-
- // 컴포넌트 언마운트 시 비디오 리소스 정리
- return () => {
- preloadedVideos.current.forEach((video) => {
- video.src = '';
- video.load();
- });
- preloadedVideos.current = [];
- };
- }, [videos]);
-
- // 비디오 변경 시 콜백 호출
- useEffect(() => {
- if (onVideoChange) {
- onVideoChange(currentVideoIndex);
- }
- }, [currentVideoIndex, onVideoChange]);
-
- // 비디오 종료 시 다음 비디오로 전환
- const handleVideoEnd = () => {
- setCurrentVideoIndex((prevIndex) => (prevIndex + 1) % videos.length);
- };
-
- // 인디케이터 클릭 핸들러
- const handleIndicatorClick = (index: number) => {
- setCurrentVideoIndex(index);
- // 비디오 즉시 재생
- if (videoRef.current) {
- videoRef.current.currentTime = 0;
- videoRef.current.play();
- }
- };
-
- return (
- <div className={cn('absolute inset-0 overflow-hidden', className)}>
- {/* 비디오 배경 */}
- <video
- ref={videoRef}
- key={currentVideoIndex}
- autoPlay
- muted
- playsInline
- onEnded={handleVideoEnd}
- className="absolute inset-0 w-full h-full object-cover"
- >
- <source src={videos[currentVideoIndex]} type="video/mp4" />
- </video>
-
- {/* 오버레이 */}
- <div className={cn('absolute inset-0 bg-black/40', overlayClassName)}></div>
-
- {/* 캐시 상태 표시 (개발용) */}
- {showCacheStatus && cachedVideos > 0 && (
- <div className="absolute top-4 right-4 z-10 bg-black/70 text-white text-xs px-3 py-2 rounded-lg backdrop-blur-sm">
- <div className="flex items-center space-x-2">
- <span>
- Cached: {cachedVideos}/{videos.length}
- </span>
- </div>
- </div>
- )}
-
- {/* 비디오 인디케이터 */}
- {showIndicators && isVideoLoaded && (
- <div className="absolute bottom-8 left-1/2 transform -translate-x-1/2 z-10 flex justify-center space-x-2">
- {videos.map((_, index) => (
- <button
- key={index}
- onClick={() => handleIndicatorClick(index)}
- className={cn(
- 'h-2 rounded-full transition-all duration-300 focus:outline-none focus:ring-2 focus:ring-white/50',
- index === currentVideoIndex
- ? 'bg-white w-8'
- : 'bg-white/50 hover:bg-white/75 w-2'
- )}
- aria-label={`비디오 ${index + 1}로 이동`}
- />
- ))}
- </div>
- )}
- </div>
- );
-}
-
diff --git a/components/login/login-form.tsx b/components/login/login-form.tsx
index fae5b8c9..51d54531 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 { InfoIcon, GlobeIcon, ChevronDownIcon, ArrowLeft } from "lucide-react";
+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'
@@ -21,7 +21,6 @@ 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 = [
@@ -45,6 +44,9 @@ 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);
@@ -81,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));
}
}, []);
@@ -163,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) {
@@ -566,59 +576,47 @@ export function LoginForm() {
}
return (
- <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 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>
+ {!isDataRoomHost && (
+ <Link
+ href="/partners/repository"
+ className={cn(buttonVariants({ variant: "ghost" }))}
+ >
+ <InfoIcon className="w-4 h-4 mr-1" />
+ {t('registerVendor')}
+ </Link>
+ )}
+ </div>
- {/* 로그인 폼 컨텐츠 */}
- <div className="p-8">
- <div className="flex flex-col gap-6">
- {/* Header */}
- <div className="flex flex-col items-center text-center">
+ {/* 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">
{!showMfaForm ? (
<>
- <h1 className="text-2xl font-bold text-foreground">{isDataRoomHost ? t('loginMessageDataRoom') :t('loginMessage')}</h1>
+ <h1 className="text-2xl font-bold">{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 dark:bg-blue-900/30 mb-4">
+ <div className="flex items-center justify-center w-12 h-12 rounded-full bg-blue-100 mb-4">
{mfaType === 'email' ? '📧' : '🔐'}
</div>
- <h1 className="text-2xl font-bold text-foreground">
+ <h1 className="text-2xl font-bold">
{mfaType === 'email' ? t('emailVerification') : t('smsVerification')}
</h1>
<p className="text-sm text-muted-foreground mt-2">
@@ -676,7 +674,7 @@ export function LoginForm() {
variant="ghost"
size="sm"
onClick={handleBackToLogin}
- className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
+ className="text-blue-600 hover:text-blue-800"
>
<ArrowLeft className="w-4 h-4 mr-1" />
{t('backToLogin')}
@@ -684,9 +682,9 @@ export function LoginForm() {
</div>
{/* 이메일 표시 */}
- <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 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>
{/* 패스워드 입력 폼 */}
@@ -724,7 +722,7 @@ export function LoginForm() {
<Button
type="button"
variant="link"
- className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm"
+ className="text-blue-600 hover:text-blue-800 text-sm"
onClick={goToVendorRegistration}
>
{t('newVendor')}
@@ -736,7 +734,7 @@ export function LoginForm() {
<Button
type="button"
variant="link"
- className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm"
+ className="text-blue-600 hover:text-blue-800 text-sm"
onClick={() => setShowForgotPassword(true)}
>
{t('forgotPassword')}
@@ -754,7 +752,7 @@ export function LoginForm() {
variant="ghost"
size="sm"
onClick={handleBackToLogin}
- className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
+ className="text-blue-600 hover:text-blue-800"
>
<ArrowLeft className="w-4 h-4 mr-1" />
{t('backToLogin')}
@@ -762,11 +760,11 @@ export function LoginForm() {
</div>
{/* OTP 재전송 섹션 (SMS/Email) */}
- <div className="bg-muted p-4 rounded-lg">
- <h3 className="text-sm font-medium text-foreground mb-2">
+ <div className="bg-gray-50 p-4 rounded-lg">
+ <h3 className="text-sm font-medium text-gray-900 mb-2">
{t('resendCode')}
</h3>
- <p className="text-xs text-muted-foreground mb-3">
+ <p className="text-xs text-gray-600 mb-3">
{mfaType === 'email'
? t('didNotReceiveEmail')
: t('didNotReceiveCode')}
@@ -799,7 +797,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-foreground mb-3">
+ <label className="block text-sm font-medium text-gray-700 mb-3">
{t('enterSixDigitCode')}
</label>
<div className="flex justify-center">
@@ -832,16 +830,16 @@ export function LoginForm() {
</form>
{/* 도움말 */}
- <div className="bg-yellow-50 dark:bg-yellow-900/20 p-3 rounded-lg border border-yellow-200 dark:border-yellow-800">
+ <div className="bg-yellow-50 p-3 rounded-lg border border-yellow-200">
<div className="flex">
<div className="flex-shrink-0">
⚠️
</div>
<div className="ml-2">
- <h4 className="text-xs font-medium text-yellow-800 dark:text-yellow-300">
+ <h4 className="text-xs font-medium text-yellow-800">
{t('didNotReceiveCode')}
</h4>
- <div className="mt-1 text-xs text-yellow-700 dark:text-yellow-400">
+ <div className="mt-1 text-xs text-yellow-700">
<ul className="list-disc list-inside space-y-1">
<li>{t('checkPhoneNumber')}</li>
<li>{t('checkSpamFolder')}</li>
@@ -856,22 +854,22 @@ export function LoginForm() {
{/* 비밀번호 재설정 다이얼로그 */}
{showForgotPassword && !showMfaForm && (
- <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="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="flex justify-between items-center mb-4">
- <h3 className="text-lg font-semibold text-foreground">{t('resetPassword')}</h3>
+ <h3 className="text-lg font-semibold">{t('resetPassword')}</h3>
<button
onClick={() => {
setShowForgotPassword(false);
}}
- className="text-muted-foreground hover:text-foreground"
+ className="text-gray-400 hover:text-gray-600"
>
</button>
</div>
<form action={passwordResetAction} className="space-y-4">
<div>
- <p className="text-sm text-muted-foreground mb-3">
+ <p className="text-sm text-gray-600 mb-3">
{t('resetDescription')}
</p>
<Input
@@ -931,23 +929,62 @@ export function LoginForm() {
</DropdownMenu>
</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 mt-4">
- {t("agreement")}{" "}
- <Link
- href={`/${lng}/privacy`}
- className="underline underline-offset-4 hover:text-primary"
- >
- {t("privacyPolicy")}
- </Link>
- </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"
+ )}
+ onClick={() => setCurrentVideoIndex(index)}
+ />
+ ))}
+ </div>
+ </div>
</div>
)
} \ No newline at end of file