diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-03 17:21:09 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-03 17:21:09 +0900 |
| commit | df5a6201bdf8ff9edfc6429b918cf2fd2b245684 (patch) | |
| tree | 2564a861f69b8803a29db7786bcf1113557ddd0f /components | |
| parent | 6653a4bdf9ac9d12037ac62cfe8c8d31d60cadd3 (diff) | |
(김준회) Revert: 로그인 영상 관련 커밋 revert 처리
Diffstat (limited to 'components')
| -rw-r--r-- | components/common/video-background.tsx | 181 | ||||
| -rw-r--r-- | components/login/login-form.tsx | 187 |
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">“{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> ) }
\ No newline at end of file |
