Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
255 changes: 201 additions & 54 deletions src/domains/auth/components/SignupForm.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
Box,
TextField,
Expand All @@ -10,110 +9,258 @@ import {
IconButton,
CircularProgress,
Link,
Divider,
LinearProgress,
} from '@mui/material';
import {
Email as EmailIcon,
Lock as LockIcon,
Visibility,
VisibilityOff,
Check as CheckIcon,
Close as CloseIcon,
} from '@mui/icons-material';
import { useAuth } from '../../../contexts/AuthContext';

export default function LoginForm({ onSwitchToSignUp }) {
const navigate = useNavigate();
const { login } = useAuth();
export default function SignupForm({ onSwitchToLogin }) {
const { register } = useAuth()

const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState('');
const [formData, setFormData] = useState({
email: '',
password: '',
confirmPassword: '',
})
const [showPassword, setShowPassword] = useState(false)
const [showConfirmPassword, setShowConfirmPassword] = useState(false)
const [error, setError] = useState('')
const [success, setSuccess] = useState('')
const [isLoading, setIsLoading] = useState(false)

const handleChange = (e) => {
const { name, value } = e.target
setFormData((prev) => ({ ...prev, [name]: value }))
setError('')
}

const isPasswordValid = passwordStrength >= 4

const handleSubmit = async (e) => {
e.preventDefault();
setError('');
e.preventDefault()
setError('')
setSuccess('')
setIsLoading(true)

// 유효성 검사
if (!formData.email || !formData.password || !formData.confirmPassword) {
setError('모든 필드를 입력해주세요.')
setIsLoading(false)
return
}
if (!isPasswordValid) {
setError('비밀번호 조건을 충족해주세요.')
setIsLoading(false)
return
}

if (!email || !password) {
setError('이메일과 비밀번호를 입력해주세요.');
return;
if (formData.password !== formData.confirmPassword) {
setError('비밀번호가 일치하지 않습니다.')
setIsLoading(false)
return
}

setIsLoading(true);
const result = await login(email, password);
try {
const result = await register(formData.email, formData.password)

if (result.success) {
navigate('/dashboard');
} else {
setError(result.message);
if (result.success) {
setSuccess(result.message)
setStep('verify')
} else {
setError(result.message)
}
} catch (err) {
setError('회원가입 중 오류가 발생했습니다.')
} finally {
setIsLoading(false)
}
setIsLoading(false);
};
}


return (
<Box component="form" onSubmit={handleSubmit}>
<Typography variant="h4" textAlign="center" fontWeight={700} mb={1}>
로그인
</Typography>
<Typography variant="body2" color="text.secondary" textAlign="center" mb={3}>
AI 언어 학습 시스템에 오신 것을 환영합니다
</Typography>
<Box
component="form"
onSubmit={handleSubmit}
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2.5,
width: '100%',
}}
>
{/* 타이틀 */}
<Box sx={{ textAlign: 'center', mb: 2 }}>
<Typography
variant="h4"
sx={{
fontWeight: 700,
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
mb: 1,
}}
>
회원가입
</Typography>
<Typography variant="body2" color="text.secondary">
새로운 계정을 만들어보세요
</Typography>
</Box>

{error && <Alert severity="error" sx={{ mb: 2 }}>{error}</Alert>}
{/* 에러/성공 메시지 */}
{error && (
<Alert severity="error" sx={{ borderRadius: 2 }}>
{error}
</Alert>
)}
{success && (
<Alert severity="success" sx={{ borderRadius: 2 }}>
{success}
</Alert>
)}

{/* 이메일 입력 */}
<TextField
fullWidth
label="이메일"
name="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
label="이메일"
placeholder="example@email.com"
value={formData.email}
onChange={handleChange}
disabled={isLoading}
sx={{ mb: 2 }}
InputProps={{
startAdornment: (
<InputAdornment position="start"><EmailIcon color="action" /></InputAdornment>
<InputAdornment position="start">
<EmailIcon color="action" />
</InputAdornment>
),
}}
/>

{/* 비밀번호 입력 */}
<Box>
<TextField
fullWidth
name="password"
type={showPassword ? 'text' : 'password'}
label="비밀번호"
placeholder="비밀번호를 입력하세요"
value={formData.password}
onChange={handleChange}
disabled={isLoading}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<LockIcon color="action" />
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton
onClick={() => setShowPassword(!showPassword)}
edge="end"
size="small"
>
{showPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>

{/* TODO : 비밀번호 강도 표시 추가 */}
</Box>

{/* 비밀번호 확인 */}
<TextField
fullWidth
label="비밀번호"
type={showPassword ? 'text' : 'password'}
value={password}
onChange={(e) => setPassword(e.target.value)}
name="confirmPassword"
type={showConfirmPassword ? 'text' : 'password'}
label="비밀번호 확인"
placeholder="비밀번호를 다시 입력하세요"
value={formData.confirmPassword}
onChange={handleChange}
disabled={isLoading}
sx={{ mb: 3 }}
error={formData.confirmPassword && formData.password !== formData.confirmPassword}
helperText={
formData.confirmPassword && formData.password !== formData.confirmPassword
? '비밀번호가 일치하지 않습니다'
: ''
}
InputProps={{
startAdornment: (
<InputAdornment position="start"><LockIcon color="action" /></InputAdornment>
<InputAdornment position="start">
<LockIcon color="action" />
</InputAdornment>
),
endAdornment: (
<InputAdornment position="end">
<IconButton onClick={() => setShowPassword(!showPassword)}>
{showPassword ? <VisibilityOff /> : <Visibility />}
<IconButton
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
edge="end"
size="small"
>
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
</IconButton>
</InputAdornment>
),
}}
/>

{/* 가입 버튼 */}
<Button
type="submit"
variant="contained"
fullWidth
size="large"
disabled={isLoading}
sx={{ mb: 2 }}
sx={{
py: 1.5,
fontSize: '1rem',
fontWeight: 600,
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
'&:hover': {
background: 'linear-gradient(135deg, #34d399 0%, #10b981 100%)',
},
}}
>
{isLoading ? <CircularProgress size={24} color="inherit" /> : '로그인'}
{isLoading ? (
<CircularProgress size={24} color="inherit" />
) : (
'인증 코드 받기'
)}
</Button>

<Typography textAlign="center">
계정이 없으신가요?{' '}
<Link component="button" type="button" onClick={onSwitchToSignUp} sx={{ fontWeight: 600 }}>
회원가입
</Link>
</Typography>
{/* 구분선 */}
<Divider sx={{ my: 1 }}>
<Typography variant="caption" color="text.secondary">
또는
</Typography>
</Divider>

{/* 로그인 안내 */}
<Box sx={{ textAlign: 'center' }}>
<Typography variant="body2" color="text.secondary">
이미 계정이 있으신가요?{' '}
<Link
component="button"
type="button"
onClick={onSwitchToLogin}
sx={{ fontWeight: 600, textDecoration: 'none' }}
>
로그인
</Link>
</Typography>
</Box>
</Box>
);
}
)
}