- Installed Material-UI (@mui/material, @emotion/react, @emotion/styled, @mui/icons-material) - Removed unused Lucide React dependency - Redesigned LoginPage with Material-UI to match Google OAuth login style - Redesigned AuthorizePage with Material-UI to match Google OAuth permission screen - Updated docker-compose to remove APISIX health check dependency from frontend 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
307 lines
8.8 KiB
TypeScript
307 lines
8.8 KiB
TypeScript
import { useState, useEffect } from 'react'
|
|
import { useNavigate } from 'react-router-dom'
|
|
import { useAuth } from '../contexts/AuthContext'
|
|
import {
|
|
Box,
|
|
Container,
|
|
TextField,
|
|
Button,
|
|
Typography,
|
|
Link,
|
|
Paper,
|
|
IconButton,
|
|
InputAdornment,
|
|
Checkbox,
|
|
FormControlLabel,
|
|
Select,
|
|
MenuItem,
|
|
Avatar,
|
|
Stack,
|
|
Divider
|
|
} from '@mui/material'
|
|
import {
|
|
Visibility,
|
|
VisibilityOff,
|
|
Language,
|
|
HelpOutline,
|
|
KeyboardArrowDown
|
|
} from '@mui/icons-material'
|
|
|
|
const LoginPage = () => {
|
|
const [email, setEmail] = useState('')
|
|
const [password, setPassword] = useState('')
|
|
const [showPassword, setShowPassword] = useState(false)
|
|
const [isLoading, setIsLoading] = useState(false)
|
|
const [rememberMe, setRememberMe] = useState(false)
|
|
const [language, setLanguage] = useState('ko')
|
|
|
|
const { login, user } = useAuth()
|
|
const navigate = useNavigate()
|
|
|
|
useEffect(() => {
|
|
if (user) {
|
|
navigate('/dashboard')
|
|
}
|
|
}, [user, navigate])
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setIsLoading(true)
|
|
|
|
try {
|
|
await login(email, password)
|
|
if (rememberMe) {
|
|
localStorage.setItem('last_user_email', email)
|
|
}
|
|
} catch (error) {
|
|
console.error('Login error:', error)
|
|
} finally {
|
|
setIsLoading(false)
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Box
|
|
sx={{
|
|
minHeight: '100vh',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
backgroundColor: '#fff'
|
|
}}
|
|
>
|
|
<Container maxWidth="sm">
|
|
<Box
|
|
sx={{
|
|
minHeight: '100vh',
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
justifyContent: 'center',
|
|
py: 4
|
|
}}
|
|
>
|
|
{/* Logo and Title */}
|
|
<Box sx={{ textAlign: 'center', mb: 4 }}>
|
|
<Typography
|
|
variant="h4"
|
|
sx={{
|
|
fontWeight: 300,
|
|
color: '#202124',
|
|
mb: 2,
|
|
fontFamily: 'Google Sans, Roboto, Arial, sans-serif'
|
|
}}
|
|
>
|
|
<Box component="span" sx={{ fontWeight: 500 }}>AiMond</Box>
|
|
</Typography>
|
|
<Typography
|
|
variant="h5"
|
|
sx={{
|
|
fontWeight: 400,
|
|
color: '#202124',
|
|
mb: 1
|
|
}}
|
|
>
|
|
AiMond Account로 로그인
|
|
</Typography>
|
|
</Box>
|
|
|
|
{/* Login Form */}
|
|
<Paper
|
|
elevation={0}
|
|
sx={{
|
|
border: '1px solid #dadce0',
|
|
borderRadius: '8px',
|
|
p: 5,
|
|
maxWidth: 450,
|
|
width: '100%',
|
|
mx: 'auto'
|
|
}}
|
|
>
|
|
<form onSubmit={handleSubmit}>
|
|
<TextField
|
|
fullWidth
|
|
type="email"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
placeholder="이메일"
|
|
required
|
|
sx={{
|
|
mb: 2,
|
|
'& .MuiOutlinedInput-root': {
|
|
'&:hover fieldset': {
|
|
borderColor: '#1976d2',
|
|
},
|
|
},
|
|
}}
|
|
size="medium"
|
|
autoComplete="email"
|
|
/>
|
|
|
|
<Link
|
|
href="#"
|
|
underline="none"
|
|
sx={{
|
|
color: '#1a73e8',
|
|
fontSize: '14px',
|
|
fontWeight: 500,
|
|
display: 'block',
|
|
mb: 3
|
|
}}
|
|
>
|
|
비밀번호를 잊으셨나요?
|
|
</Link>
|
|
|
|
<TextField
|
|
fullWidth
|
|
type={showPassword ? 'text' : 'password'}
|
|
value={password}
|
|
onChange={(e) => setPassword(e.target.value)}
|
|
placeholder="비밀번호를 입력하세요"
|
|
required
|
|
sx={{ mb: 1 }}
|
|
size="medium"
|
|
InputProps={{
|
|
endAdornment: (
|
|
<InputAdornment position="end">
|
|
<IconButton
|
|
onClick={() => setShowPassword(!showPassword)}
|
|
edge="end"
|
|
size="small"
|
|
>
|
|
{showPassword ? <VisibilityOff /> : <Visibility />}
|
|
</IconButton>
|
|
</InputAdornment>
|
|
),
|
|
}}
|
|
/>
|
|
|
|
<FormControlLabel
|
|
control={
|
|
<Checkbox
|
|
checked={rememberMe}
|
|
onChange={(e) => setRememberMe(e.target.checked)}
|
|
size="small"
|
|
sx={{ color: '#5f6368' }}
|
|
/>
|
|
}
|
|
label={
|
|
<Typography sx={{ fontSize: '14px', color: '#5f6368' }}>
|
|
로그인 상태 유지
|
|
</Typography>
|
|
}
|
|
sx={{ mb: 4 }}
|
|
/>
|
|
|
|
<Typography
|
|
sx={{
|
|
fontSize: '14px',
|
|
color: '#5f6368',
|
|
mb: 4,
|
|
lineHeight: 1.5
|
|
}}
|
|
>
|
|
하나의 AiMond 계정으로 모든 AiMond 서비스를 이용하실 수 있습니다
|
|
</Typography>
|
|
|
|
{/* Service Icons */}
|
|
<Stack direction="row" spacing={1} justifyContent="center" sx={{ mb: 4 }}>
|
|
<Avatar sx={{ width: 32, height: 32, bgcolor: '#4285f4', fontSize: 14 }}>A</Avatar>
|
|
<Avatar sx={{ width: 32, height: 32, bgcolor: '#ea4335', fontSize: 14 }}>M</Avatar>
|
|
<Avatar sx={{ width: 32, height: 32, bgcolor: '#34a853', fontSize: 14 }}>D</Avatar>
|
|
<Avatar sx={{ width: 32, height: 32, bgcolor: '#fbbc04', fontSize: 14 }}>S</Avatar>
|
|
<Avatar sx={{ width: 32, height: 32, bgcolor: '#9333ea', fontSize: 14 }}>C</Avatar>
|
|
</Stack>
|
|
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<Link
|
|
href="/signup"
|
|
underline="none"
|
|
sx={{
|
|
color: '#1a73e8',
|
|
fontSize: '14px',
|
|
fontWeight: 500
|
|
}}
|
|
>
|
|
계정 만들기
|
|
</Link>
|
|
<Button
|
|
type="submit"
|
|
variant="contained"
|
|
disabled={isLoading}
|
|
sx={{
|
|
backgroundColor: '#1a73e8',
|
|
color: 'white',
|
|
textTransform: 'none',
|
|
px: 3,
|
|
py: 1,
|
|
fontSize: '14px',
|
|
fontWeight: 500,
|
|
'&:hover': {
|
|
backgroundColor: '#1666c9',
|
|
},
|
|
}}
|
|
>
|
|
로그인
|
|
</Button>
|
|
</Box>
|
|
</form>
|
|
</Paper>
|
|
|
|
{/* Language Selection Link */}
|
|
<Box sx={{ textAlign: 'center', mt: 3 }}>
|
|
<Link
|
|
href="#"
|
|
underline="none"
|
|
sx={{
|
|
color: '#1a73e8',
|
|
fontSize: '14px',
|
|
display: 'inline-flex',
|
|
alignItems: 'center',
|
|
gap: 0.5
|
|
}}
|
|
>
|
|
도움말개인정보처리방침약관
|
|
</Link>
|
|
</Box>
|
|
</Box>
|
|
</Container>
|
|
|
|
{/* Footer */}
|
|
<Box
|
|
sx={{
|
|
borderTop: '1px solid #dadce0',
|
|
py: 2,
|
|
px: 3,
|
|
backgroundColor: '#f8f9fa'
|
|
}}
|
|
>
|
|
<Container maxWidth="lg">
|
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
|
<Select
|
|
value={language}
|
|
onChange={(e) => setLanguage(e.target.value)}
|
|
variant="standard"
|
|
disableUnderline
|
|
sx={{ fontSize: '12px', color: '#5f6368' }}
|
|
>
|
|
<MenuItem value="ko">한국어</MenuItem>
|
|
<MenuItem value="en">English</MenuItem>
|
|
</Select>
|
|
<Stack direction="row" spacing={3}>
|
|
<Link href="#" underline="none" sx={{ fontSize: '12px', color: '#5f6368' }}>
|
|
도움말
|
|
</Link>
|
|
<Link href="#" underline="none" sx={{ fontSize: '12px', color: '#5f6368' }}>
|
|
개인정보처리방침
|
|
</Link>
|
|
<Link href="#" underline="none" sx={{ fontSize: '12px', color: '#5f6368' }}>
|
|
약관
|
|
</Link>
|
|
</Stack>
|
|
</Box>
|
|
</Container>
|
|
</Box>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
export default LoginPage |