diff --git a/oauth/frontend/src/App.tsx b/oauth/frontend/src/App.tsx index b19eae9..60e3e57 100644 --- a/oauth/frontend/src/App.tsx +++ b/oauth/frontend/src/App.tsx @@ -1,6 +1,7 @@ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import LoginPage from './pages/LoginPage' +import SignupPage from './pages/SignupPage' import Dashboard from './pages/Dashboard' import Applications from './pages/Applications' import Profile from './pages/Profile' @@ -26,6 +27,7 @@ function App() { } /> + } /> } /> { + const [formData, setFormData] = useState({ + name: '', + email: '', + password: '', + confirmPassword: '', + organization: '', + agreeTerms: false + }) + const [showPassword, setShowPassword] = useState(false) + const [showConfirmPassword, setShowConfirmPassword] = useState(false) + const [isLoading, setIsLoading] = useState(false) + const [focusedInput, setFocusedInput] = useState(null) + const [passwordStrength, setPasswordStrength] = useState(0) + + const navigate = useNavigate() + + const checkPasswordStrength = (password: string) => { + let strength = 0 + if (password.length >= 8) strength++ + if (password.match(/[a-z]/) && password.match(/[A-Z]/)) strength++ + if (password.match(/[0-9]/)) strength++ + if (password.match(/[^a-zA-Z0-9]/)) strength++ + setPasswordStrength(strength) + } + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value, type, checked } = e.target + const newValue = type === 'checkbox' ? checked : value + + setFormData(prev => ({ + ...prev, + [name]: newValue + })) + + if (name === 'password') { + checkPasswordStrength(value) + } + } + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + + if (formData.password !== formData.confirmPassword) { + alert('비밀번호가 일치하지 않습니다.') + return + } + + setIsLoading(true) + + try { + const response = await fetch('/api/v1/auth/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: formData.name, + email: formData.email, + password: formData.password, + organization: formData.organization + }), + }) + + if (response.ok) { + navigate('/login') + } + } catch (error) { + console.error('Signup error:', error) + } finally { + setIsLoading(false) + } + } + + const getPasswordStrengthColor = () => { + switch(passwordStrength) { + case 0: return 'bg-gray-400' + case 1: return 'bg-red-500' + case 2: return 'bg-yellow-500' + case 3: return 'bg-blue-500' + case 4: return 'bg-green-500' + default: return 'bg-gray-400' + } + } + + const getPasswordStrengthText = () => { + switch(passwordStrength) { + case 0: return '' + case 1: return '약함' + case 2: return '보통' + case 3: return '강함' + case 4: return '매우 강함' + default: return '' + } + } + + return ( +
+ {/* Animated background elements */} +
+
+
+
+
+ + {/* Grid pattern overlay */} +
+ + {/* Main content */} +
+
+ + {/* Left side - Branding */} +
+
+
+
+ +
+

+ AiMond Authorization +

+
+

+ 새로운 계정을 생성하고 시작하세요 +

+
+ +
+
+
+ +
+
+

빠른 가입 절차

+

몇 가지 정보만으로 간단하게 가입 완료

+
+
+ +
+
+ +
+
+

엔터프라이즈 보안

+

최고 수준의 보안으로 데이터 보호

+
+
+ +
+
+ +
+
+

조직 단위 관리

+

팀과 조직을 효율적으로 관리

+
+
+
+
+ + {/* Right side - Signup Form */} +
+
+ {/* Form Header */} +
+

Create Account

+

새로운 계정을 생성하세요

+
+ + {/* Signup Form */} +
+ {/* Name Input */} +
+ +
+ setFocusedInput('name')} + onBlur={() => setFocusedInput(null)} + className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-400 focus:bg-white/10 transition-all duration-300" + placeholder="홍길동" + disabled={isLoading} + /> +
+
+
+ + {/* Email Input */} +
+ +
+ setFocusedInput('email')} + onBlur={() => setFocusedInput(null)} + className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-400 focus:bg-white/10 transition-all duration-300" + placeholder="your@email.com" + disabled={isLoading} + /> +
+
+
+ + {/* Organization Input */} +
+ +
+ setFocusedInput('organization')} + onBlur={() => setFocusedInput(null)} + className="w-full px-4 py-3 bg-white/5 border border-white/10 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-400 focus:bg-white/10 transition-all duration-300" + placeholder="AiMond Inc." + disabled={isLoading} + /> +
+
+
+ + {/* Password Input */} +
+ +
+ setFocusedInput('password')} + onBlur={() => setFocusedInput(null)} + className="w-full px-4 py-3 pr-12 bg-white/5 border border-white/10 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-400 focus:bg-white/10 transition-all duration-300" + placeholder="••••••••" + disabled={isLoading} + /> + +
+
+ {formData.password && ( +
+
+
+
+ {getPasswordStrengthText()} +
+ )} +
+ + {/* Confirm Password Input */} +
+ +
+ setFocusedInput('confirmPassword')} + onBlur={() => setFocusedInput(null)} + className="w-full px-4 py-3 pr-12 bg-white/5 border border-white/10 rounded-xl text-white placeholder-gray-400 focus:outline-none focus:border-purple-400 focus:bg-white/10 transition-all duration-300" + placeholder="••••••••" + disabled={isLoading} + /> + +
+
+
+ + {/* Terms Agreement */} +
+ + +
+ + {/* Submit Button */} + +
+ + {/* Sign in link */} +

+ 이미 계정이 있으신가요?{' '} + + 로그인하기 + +

+
+ + {/* Security Badge */} +
+ + 256-bit SSL 암호화로 보호됨 +
+
+
+
+
+ ) +} + +export default SignupPage \ No newline at end of file diff --git a/ref/image19.png b/ref/image19.png new file mode 100644 index 0000000..8ff5783 Binary files /dev/null and b/ref/image19.png differ diff --git a/ref/image20.png b/ref/image20.png new file mode 100644 index 0000000..0086099 Binary files /dev/null and b/ref/image20.png differ