1+ 'use client' ;
2+
3+ import { useState } from 'react' ;
4+ import { useAuth } from '@/contexts/AuthContext' ;
5+ import { Eye , EyeOff , Mail , Lock , ShoppingBag } from 'lucide-react' ;
6+ import Link from 'next/link' ;
7+
8+ export default function LoginPage ( ) {
9+ const [ isSignUp , setIsSignUp ] = useState ( false ) ;
10+ const [ email , setEmail ] = useState ( '' ) ;
11+ const [ password , setPassword ] = useState ( '' ) ;
12+ const [ name , setName ] = useState ( '' ) ;
13+ const [ showPassword , setShowPassword ] = useState ( false ) ;
14+ const [ loading , setLoading ] = useState ( false ) ;
15+ const [ error , setError ] = useState < string | null > ( null ) ;
16+
17+ const { signIn, signUp, signInWithGoogle } = useAuth ( ) ;
18+
19+ const handleSubmit = async ( e : React . FormEvent ) => {
20+ e . preventDefault ( ) ;
21+ setError ( null ) ;
22+ setLoading ( true ) ;
23+
24+ try {
25+ if ( isSignUp ) {
26+ await signUp ( email , password , name ) ;
27+ } else {
28+ await signIn ( email , password ) ;
29+ }
30+ } catch ( err : any ) {
31+ setError ( err . message || '오류가 발생했습니다' ) ;
32+ } finally {
33+ setLoading ( false ) ;
34+ }
35+ } ;
36+
37+ const handleGoogleSignIn = async ( ) => {
38+ setError ( null ) ;
39+ setLoading ( true ) ;
40+
41+ try {
42+ await signInWithGoogle ( ) ;
43+ } catch ( err : any ) {
44+ setError ( err . message || 'Google 로그인 실패' ) ;
45+ setLoading ( false ) ;
46+ }
47+ } ;
48+
49+ return (
50+ < div className = "min-h-screen bg-gradient-to-br from-orange-100 via-pink-50 to-purple-100 flex items-center justify-center p-4" >
51+ < div className = "w-full max-w-md" >
52+ { /* 로고 */ }
53+ < div className = "text-center mb-8" >
54+ < div className = "inline-flex items-center justify-center w-20 h-20 bg-gradient-to-br from-orange-500 to-pink-500 rounded-3xl shadow-lg mb-4" >
55+ < ShoppingBag className = "w-10 h-10 text-white" />
56+ </ div >
57+ < h1 className = "text-3xl font-bold text-gray-900" > QuickMart</ h1 >
58+ < p className = "text-gray-600 mt-2" > 30분 내 초고속 배송</ p >
59+ </ div >
60+
61+ { /* 로그인 폼 */ }
62+ < div className = "bg-white rounded-3xl shadow-xl p-8" >
63+ < h2 className = "text-2xl font-bold text-gray-900 mb-6" >
64+ { isSignUp ? '회원가입' : '로그인' }
65+ </ h2 >
66+
67+ { error && (
68+ < div className = "bg-red-50 text-red-600 p-3 rounded-xl mb-4 text-sm" >
69+ { error }
70+ </ div >
71+ ) }
72+
73+ < form onSubmit = { handleSubmit } className = "space-y-4" >
74+ { isSignUp && (
75+ < div >
76+ < label className = "block text-sm font-medium text-gray-700 mb-2" >
77+ 이름
78+ </ label >
79+ < input
80+ type = "text"
81+ value = { name }
82+ onChange = { ( e ) => setName ( e . target . value ) }
83+ className = "w-full px-4 py-3 border border-gray-200 rounded-2xl focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
84+ placeholder = "홍길동"
85+ />
86+ </ div >
87+ ) }
88+
89+ < div >
90+ < label className = "block text-sm font-medium text-gray-700 mb-2" >
91+ 이메일
92+ </ label >
93+ < div className = "relative" >
94+ < input
95+ type = "email"
96+ value = { email }
97+ onChange = { ( e ) => setEmail ( e . target . value ) }
98+ className = "w-full pl-12 pr-4 py-3 border border-gray-200 rounded-2xl focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
99+ placeholder = "example@email.com"
100+ required
101+ />
102+ < Mail className = "absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
103+ </ div >
104+ </ div >
105+
106+ < div >
107+ < label className = "block text-sm font-medium text-gray-700 mb-2" >
108+ 비밀번호
109+ </ label >
110+ < div className = "relative" >
111+ < input
112+ type = { showPassword ? 'text' : 'password' }
113+ value = { password }
114+ onChange = { ( e ) => setPassword ( e . target . value ) }
115+ className = "w-full pl-12 pr-12 py-3 border border-gray-200 rounded-2xl focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent"
116+ placeholder = "••••••••"
117+ required
118+ minLength = { 6 }
119+ />
120+ < Lock className = "absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-gray-400" />
121+ < button
122+ type = "button"
123+ onClick = { ( ) => setShowPassword ( ! showPassword ) }
124+ className = "absolute right-4 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600"
125+ >
126+ { showPassword ? < EyeOff className = "w-5 h-5" /> : < Eye className = "w-5 h-5" /> }
127+ </ button >
128+ </ div >
129+ </ div >
130+
131+ < button
132+ type = "submit"
133+ disabled = { loading }
134+ className = "w-full bg-gradient-to-r from-orange-500 to-pink-500 text-white py-3 rounded-2xl font-semibold hover:shadow-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed"
135+ >
136+ { loading ? '처리 중...' : isSignUp ? '회원가입' : '로그인' }
137+ </ button >
138+ </ form >
139+
140+ { /* 구분선 */ }
141+ < div className = "relative my-6" >
142+ < div className = "absolute inset-0 flex items-center" >
143+ < div className = "w-full border-t border-gray-200" > </ div >
144+ </ div >
145+ < div className = "relative flex justify-center text-sm" >
146+ < span className = "px-4 bg-white text-gray-500" > 또는</ span >
147+ </ div >
148+ </ div >
149+
150+ { /* 소셜 로그인 */ }
151+ < button
152+ onClick = { handleGoogleSignIn }
153+ disabled = { loading }
154+ className = "w-full bg-white border border-gray-200 text-gray-700 py-3 rounded-2xl font-semibold hover:bg-gray-50 transition-colors flex items-center justify-center gap-3 disabled:opacity-50 disabled:cursor-not-allowed"
155+ >
156+ < svg className = "w-5 h-5" viewBox = "0 0 24 24" >
157+ < path fill = "#4285F4" d = "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" />
158+ < path fill = "#34A853" d = "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" />
159+ < path fill = "#FBBC05" d = "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" />
160+ < path fill = "#EA4335" d = "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" />
161+ </ svg >
162+ Google로 계속하기
163+ </ button >
164+
165+ { /* 회원가입/로그인 전환 */ }
166+ < p className = "text-center text-sm text-gray-600 mt-6" >
167+ { isSignUp ? '이미 계정이 있으신가요?' : '아직 회원이 아니신가요?' }
168+ < button
169+ type = "button"
170+ onClick = { ( ) => {
171+ setIsSignUp ( ! isSignUp ) ;
172+ setError ( null ) ;
173+ } }
174+ className = "ml-2 text-orange-600 font-semibold hover:text-orange-700"
175+ >
176+ { isSignUp ? '로그인' : '회원가입' }
177+ </ button >
178+ </ p >
179+ </ div >
180+
181+ { /* 게스트 모드 */ }
182+ < div className = "text-center mt-6" >
183+ < Link
184+ href = "/"
185+ className = "text-gray-600 hover:text-gray-800 text-sm underline"
186+ >
187+ 로그인 없이 둘러보기
188+ </ Link >
189+ </ div >
190+ </ div >
191+ </ div >
192+ ) ;
193+ }
0 commit comments