Skip to content

Commit f2b380e

Browse files
authored
Merge pull request #5 from wafflestudio/feature/signup
feat: routing + signup page
2 parents bcc69fe + 0f9c0d1 commit f2b380e

File tree

9 files changed

+361
-62
lines changed

9 files changed

+361
-62
lines changed

.github/pull_request_template.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
## PR 요약

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
},
1212
"dependencies": {
1313
"react": "^19.2.0",
14-
"react-dom": "^19.2.0"
14+
"react-dom": "^19.2.0",
15+
"react-router-dom": "^7.11.0"
1516
},
1617
"devDependencies": {
1718
"@eslint/js": "^9.39.1",

src/App.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
import { TestPage } from '@/pages/TestPage';
1+
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
2+
import { MainPage } from '@/pages/MainPage';
3+
import { SignupPage } from '@/pages/SignupPage';
24

35
function App() {
46
return (
5-
<>
6-
<TestPage />
7-
</>
7+
<Router>
8+
<Routes>
9+
<Route path="/" element={<MainPage />} />
10+
<Route path="/signup" element={<SignupPage />} />
11+
<Route path="/login" element={<Navigate to="/" replace />} /> {/* Placeholder */}
12+
</Routes>
13+
</Router>
814
);
915
}
1016

src/api/client.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
11
const BASE_URL = import.meta.env.PROD ? 'http://3.34.178.145:8000' : '/api';
22

3+
// Types
4+
interface SignupRequest {
5+
name: string;
6+
email: string;
7+
password: string;
8+
}
9+
10+
interface SignupResponse {
11+
id: string;
12+
name: string;
13+
email: string;
14+
}
15+
16+
interface ErrorResponse {
17+
detail: string;
18+
}
19+
320
export const checkBackendStatus = async (): Promise<{ status: string; ok: boolean }> => {
421
try {
5-
const response = await fetch(`${BASE_URL}/test`);
22+
const response = await fetch(`${BASE_URL}/health`);
623
if (response.status === 200) {
724
return { status: 'Success', ok: true };
825
} else {
@@ -13,3 +30,26 @@ export const checkBackendStatus = async (): Promise<{ status: string; ok: boolea
1330
return { status: 'Connection Failed', ok: false };
1431
}
1532
};
33+
34+
export const signup = async (data: SignupRequest): Promise<{ success: boolean; data?: SignupResponse; error?: string }> => {
35+
try {
36+
const response = await fetch(`${BASE_URL}/api/users/signup`, {
37+
method: 'POST',
38+
headers: {
39+
'Content-Type': 'application/json',
40+
},
41+
body: JSON.stringify(data),
42+
});
43+
44+
if (response.status === 201) {
45+
const result = await response.json();
46+
return { success: true, data: result };
47+
} else {
48+
const errorData: ErrorResponse = await response.json();
49+
return { success: false, error: errorData.detail || 'Signup failed' };
50+
}
51+
} catch (error) {
52+
console.error('Signup error:', error);
53+
return { success: false, error: 'Network error occurred' };
54+
}
55+
};

src/pages/MainPage.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useState } from 'react';
2+
import { useNavigate } from 'react-router-dom';
3+
import { checkBackendStatus } from '@/api/client';
4+
import '@/styles/App.css';
5+
6+
export function MainPage() {
7+
const [status, setStatus] = useState<string>('');
8+
const navigate = useNavigate();
9+
10+
const handleCheck = async () => {
11+
const result = await checkBackendStatus();
12+
setStatus(result.status);
13+
};
14+
15+
return (
16+
<div className="container">
17+
<header className="app-header">
18+
<div className="logo">자산관리</div>
19+
<div className="auth-buttons">
20+
<button className="text-btn" onClick={() => navigate('/signup')}>회원가입</button>
21+
<button className="text-btn" onClick={() => navigate('/login')}>로그인</button>
22+
</div>
23+
</header>
24+
25+
<main className="main-content">
26+
<h1>Club Asset Management</h1>
27+
<div className="card">
28+
<button onClick={handleCheck} className="primary-btn">
29+
Check Backend Status
30+
</button>
31+
{status && (
32+
<p className={`status ${status === 'Success' ? 'success' : 'error'}`}>
33+
{status}
34+
</p>
35+
)}
36+
</div>
37+
</main>
38+
</div>
39+
);
40+
}

src/pages/SignupPage.tsx

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { useState } from 'react';
2+
import { useNavigate } from 'react-router-dom';
3+
import { signup } from '@/api/client';
4+
import '@/styles/App.css';
5+
6+
export function SignupPage() {
7+
const navigate = useNavigate();
8+
const [formData, setFormData] = useState({
9+
name: '',
10+
email: '',
11+
password: '',
12+
confirmPassword: '',
13+
});
14+
const [error, setError] = useState<string>('');
15+
16+
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
17+
const { name, value } = e.target;
18+
setFormData(prev => ({ ...prev, [name]: value }));
19+
};
20+
21+
const handleSubmit = async (e: React.FormEvent) => {
22+
e.preventDefault();
23+
setError('');
24+
25+
if (formData.password !== formData.confirmPassword) {
26+
setError('비밀번호가 일치하지 않습니다.');
27+
return;
28+
}
29+
30+
if (formData.password.length < 8) {
31+
setError('비밀번호는 8자 이상이어야 합니다.');
32+
return;
33+
}
34+
35+
const result = await signup({
36+
name: formData.name,
37+
email: formData.email,
38+
password: formData.password
39+
});
40+
41+
if (result.success) {
42+
alert('회원가입 성공!');
43+
navigate('/login');
44+
} else {
45+
setError(result.error || '회원가입에 실패했습니다.');
46+
}
47+
};
48+
49+
return (
50+
<div className="container">
51+
<header className="app-header">
52+
<div className="logo" onClick={() => navigate('/')}>자산관리</div>
53+
<div className="auth-buttons">
54+
<button className="text-btn active">회원가입</button>
55+
<button className="text-btn" onClick={() => navigate('/login')}>로그인</button>
56+
</div>
57+
</header>
58+
59+
<main className="auth-container">
60+
<h2>회원가입</h2>
61+
<form onSubmit={handleSubmit} className="auth-form">
62+
<div className="form-group">
63+
<label htmlFor="name">이름</label>
64+
<input
65+
type="text"
66+
id="name"
67+
name="name"
68+
value={formData.name}
69+
onChange={handleChange}
70+
required
71+
maxLength={30}
72+
placeholder="이름을 입력하세요"
73+
/>
74+
</div>
75+
76+
<div className="form-group">
77+
<label htmlFor="password">비밀번호</label>
78+
<input
79+
type="password"
80+
id="password"
81+
name="password"
82+
value={formData.password}
83+
onChange={handleChange}
84+
required
85+
minLength={8}
86+
placeholder="8자 이상 입력하세요"
87+
/>
88+
</div>
89+
90+
<div className="form-group">
91+
<label htmlFor="confirmPassword">비밀번호 확인</label>
92+
<input
93+
type="password"
94+
id="confirmPassword"
95+
name="confirmPassword"
96+
value={formData.confirmPassword}
97+
onChange={handleChange}
98+
required
99+
placeholder="다시 한번 입력하세요"
100+
/>
101+
</div>
102+
103+
<div className="form-group">
104+
<label htmlFor="email">이메일</label>
105+
<input
106+
type="email"
107+
id="email"
108+
name="email"
109+
value={formData.email}
110+
onChange={handleChange}
111+
required
112+
placeholder="example@email.com"
113+
/>
114+
</div>
115+
116+
{error && <p className="error-message">{error}</p>}
117+
118+
<button type="submit" className="submit-btn">회원가입</button>
119+
</form>
120+
</main>
121+
</div>
122+
);
123+
}

src/pages/TestPage.tsx

Lines changed: 0 additions & 28 deletions
This file was deleted.

0 commit comments

Comments
 (0)