Skip to content

Commit d183ada

Browse files
authored
Merge pull request #35 from XRPL-Hackathon/31-chore-서버-api-연결-세팅
31 chore 서버 api 연결 세팅
2 parents 51cd72f + 539ccd2 commit d183ada

11 files changed

Lines changed: 1035 additions & 133 deletions

File tree

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,19 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13+
"amazon-cognito-identity-js": "^6.3.12",
14+
"axios": "^1.8.4",
1315
"msw": "^2.7.3",
1416
"path": "^0.12.7",
1517
"react": "^19.0.0",
1618
"react-dom": "^19.0.0",
1719
"react-router-dom": "^7.3.0",
18-
"styled-components": "^6.1.16"
20+
"styled-components": "^6.1.16",
21+
"ts-node": "^10.9.2"
1922
},
2023
"devDependencies": {
2124
"@eslint/js": "^9.21.0",
22-
"@types/node": "^22.13.10",
25+
"@types/node": "^22.13.11",
2326
"@types/react": "^19.0.10",
2427
"@types/react-dom": "^19.0.4",
2528
"@types/react-router-dom": "^5.3.3",

src/api/index.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import axios from "axios";
2+
3+
const API_BASE_URL =
4+
"https://5erhg0u08g.execute-api.ap-northeast-2.amazonaws.com";
5+
6+
const API = axios.create({
7+
baseURL: API_BASE_URL,
8+
});
9+
10+
// 요청 시 Authorization 헤더 추가
11+
API.interceptors.request.use((config) => {
12+
const token = localStorage.getItem("access_token");
13+
if (token) {
14+
config.headers.Authorization = `Bearer ${token}`;
15+
}
16+
return config;
17+
});
18+
19+
export default API;

src/hooks/uaeSignup.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import * as AWSCognitoIdentity from "amazon-cognito-identity-js";
2+
import signIn from "@/hooks/usrLogin";
3+
4+
const userPoolData: AWSCognitoIdentity.ICognitoUserPoolData = {
5+
UserPoolId: "ap-northeast-2_36GFLZBtI",
6+
ClientId: "7j09fmn00udb6fpt8hhij1jlqk",
7+
};
8+
9+
export async function signUp({
10+
Username,
11+
Password,
12+
Email,
13+
}: {
14+
Username: string;
15+
Password: string;
16+
Email: string;
17+
}): Promise<{ message: string }> {
18+
/*
19+
* Required attributes를 추가
20+
* */
21+
const attributeData: AWSCognitoIdentity.ICognitoUserAttributeData = {
22+
Name: "nickname",
23+
Value: Username,
24+
};
25+
26+
let attributeList: AWSCognitoIdentity.CognitoUserAttribute[] = [
27+
new AWSCognitoIdentity.CognitoUserAttribute(attributeData),
28+
];
29+
/*
30+
* CognitoUserPool.signUp() 함수에 다음과 같이 Username, Password, Required attributes를 전달
31+
* 콜백함수를 통해 결과를 반환
32+
* */
33+
return await new Promise((resolve, reject) => {
34+
const userPool = new AWSCognitoIdentity.CognitoUserPool(userPoolData);
35+
36+
userPool.signUp(
37+
Email,
38+
Password,
39+
attributeList,
40+
attributeList,
41+
(
42+
err: Error | undefined,
43+
result: AWSCognitoIdentity.ISignUpResult | undefined
44+
): void => {
45+
if (err) reject({ message: err.message || JSON.stringify(err) });
46+
else {
47+
resolve({
48+
message:
49+
result?.user.getUsername() +
50+
"님, 회원 가입이 성공적으로 완료되었습니다.",
51+
});
52+
53+
signIn(Email, Password);
54+
55+
while (!localStorage.getItem("access_token")) {
56+
continue;
57+
}
58+
59+
console.log(
60+
"AccessToken in signup:",
61+
localStorage.getItem("access_token")
62+
);
63+
}
64+
}
65+
);
66+
});
67+
}

src/hooks/usrLogin.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import {
2+
CognitoUserPool,
3+
CognitoUser,
4+
AuthenticationDetails,
5+
} from "amazon-cognito-identity-js";
6+
7+
const poolData = {
8+
UserPoolId: "ap-northeast-2_36GFLZBtI",
9+
ClientId: "7j09fmn00udb6fpt8hhij1jlqk",
10+
};
11+
const userPool = new CognitoUserPool(poolData);
12+
13+
const signIn = (username: string, password: string) => {
14+
return new Promise((resolve, reject) => {
15+
const user = new CognitoUser({
16+
Username: username,
17+
Pool: userPool,
18+
});
19+
20+
const authDetails = new AuthenticationDetails({
21+
Username: username,
22+
Password: password,
23+
});
24+
25+
user.authenticateUser(authDetails, {
26+
onSuccess: (session) => {
27+
console.log("로그인 성공", session);
28+
const accessToken = session.getAccessToken().getJwtToken();
29+
localStorage.setItem("access_token", accessToken);
30+
console.log("AccessToken in login:", accessToken);
31+
32+
resolve(accessToken);
33+
},
34+
onFailure: (err) => {
35+
console.error("로그인 실패:", err.message);
36+
reject(err);
37+
},
38+
});
39+
});
40+
};
41+
42+
export default signIn;

src/pages/LoginPage/LoginPage.style.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import styled from 'styled-components';
1+
import styled from "styled-components";
22

33
// 전체 컨테이너
44
export const AppContainer = styled.div`
5-
font-family: 'Pretendard', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
5+
font-family: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI",
6+
Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
67
width: 100vw;
78
min-height: 100vh;
89
margin: 0;
@@ -38,7 +39,7 @@ export const Main = styled.main`
3839
display: flex;
3940
justify-content: center;
4041
align-items: center;
41-
min-height: calc(100vh - 60px);
42+
min-height: calc(100vh - 200px);
4243
padding: 20px;
4344
width: 100%;
4445
box-sizing: border-box;
@@ -144,7 +145,7 @@ export const ForgotPassword = styled.a`
144145
font-size: 13px;
145146
color: #555;
146147
text-decoration: none;
147-
148+
148149
&:hover {
149150
text-decoration: underline;
150151
}
@@ -178,8 +179,8 @@ export const RegisterLink = styled.a`
178179
color: #4a7bff;
179180
text-decoration: none;
180181
font-weight: 500;
181-
182+
182183
&:hover {
183184
text-decoration: underline;
184185
}
185-
`;
186+
`;

src/pages/LoginPage/LoginPage.tsx

Lines changed: 105 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,145 @@
1-
// App.tsx
2-
import React from 'react';
3-
import LoginIllustration from '../../assets/login-illustration.svg';
4-
import BookVector from '../../assets/book_vector.svg'
5-
import { useNavigate } from 'react-router-dom';
1+
import { useState } from "react";
2+
import LoginIllustration from "../../assets/login-illustration.svg";
3+
import logo from "../../assets/image/logo.svg";
4+
import { useNavigate } from "react-router-dom";
5+
import signIn from "@/hooks/usrLogin";
66

77
// Styled Components
88
import {
9-
AppContainer, Header, HeaderContent, BookLogo, HeaderLogo,
10-
Main, ContentContainer, ImageSection, FormSection,
11-
WelcomeText, Form, InputGroup, Input, PasswordInput,
12-
EyeIcon, RememberSection, CheckboxLabel, Checkbox,
13-
ForgotPassword, LoginButton, RegisterSection, RegisterLink
14-
} from './LoginPage.style';
9+
AppContainer,
10+
Header,
11+
Main,
12+
ContentContainer,
13+
ImageSection,
14+
FormSection,
15+
WelcomeText,
16+
Form,
17+
InputGroup,
18+
Input,
19+
PasswordInput,
20+
EyeIcon,
21+
RememberSection,
22+
CheckboxLabel,
23+
Checkbox,
24+
ForgotPassword,
25+
LoginButton,
26+
RegisterSection,
27+
RegisterLink,
28+
} from "./LoginPage.style";
1529

1630
const LoginPage: React.FC = () => {
1731
let navigate = useNavigate();
1832

33+
const [formData, setFormData] = useState({
34+
email: "",
35+
password: "",
36+
});
37+
const [error, setError] = useState<string>("");
38+
39+
// 이메일, 비밀번호 입력값 상태 업데이트 함수
40+
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
41+
const { name, value } = e.target;
42+
setFormData((prev) => ({
43+
...prev,
44+
[name]: value,
45+
}));
46+
};
47+
48+
const handleLogin = async (e: React.FormEvent) => {
49+
e.preventDefault(); // 폼 제출 기본 동작 방지
50+
51+
if (!formData.email || !formData.password) {
52+
setError("이메일과 비밀번호를 모두 입력해주세요.");
53+
return;
54+
}
55+
56+
try {
57+
// 로그인 API 호출
58+
await signIn(formData.email, formData.password);
59+
60+
if (localStorage.getItem("access_token")) {
61+
// 로그인 성공 시, 대시보드로 이동
62+
navigate("/main");
63+
} else {
64+
setError("로그인 실패. 이메일 또는 비밀번호를 확인해주세요.");
65+
}
66+
} catch (err: any) {
67+
console.error("로그인 중 오류 발생:", err);
68+
setError("로그인 중 오류가 발생했습니다. 다시 시도해주세요.");
69+
}
70+
};
71+
1972
return (
2073
<AppContainer>
2174
<Header>
22-
<HeaderContent>
23-
<BookLogo src={BookVector} alt="Book Logo" />
24-
<HeaderLogo>XRPedia</HeaderLogo>
25-
</HeaderContent>
75+
<img src={logo} alt="Book Logo" />
2676
</Header>
27-
77+
2878
<Main>
2979
<ContentContainer>
3080
<ImageSection>
31-
<img
32-
src={LoginIllustration}
33-
alt="Woman interacting with documents"
34-
style={{ maxWidth: '100%', height: 'auto' }}
81+
<img
82+
src={LoginIllustration}
83+
alt="Woman interacting with documents"
84+
style={{ maxWidth: "100%", height: "auto" }}
3585
/>
3686
</ImageSection>
37-
87+
3888
<FormSection>
39-
<WelcomeText>
40-
XRPedia에 온 것을 환영해요! 👋
41-
</WelcomeText>
42-
43-
<Form>
89+
<WelcomeText>XRPedia에 온 것을 환영해요! 👋</WelcomeText>
90+
91+
<Form onSubmit={handleLogin}>
92+
{error && <p style={{ color: "red" }}>{error}</p>}
4493
<InputGroup>
45-
<Input type="email" placeholder="e-mail" />
94+
<Input
95+
type="email"
96+
name="email"
97+
placeholder="e-mail"
98+
value={formData.email}
99+
onChange={handleInputChange}
100+
/>
46101
</InputGroup>
47-
102+
48103
<PasswordInput>
49-
<Input type="password" placeholder="password" />
104+
<Input
105+
type="password"
106+
name="password"
107+
placeholder="password"
108+
value={formData.password}
109+
onChange={handleInputChange}
110+
/>
50111
<EyeIcon>
51-
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
112+
<svg
113+
width="16"
114+
height="16"
115+
viewBox="0 0 24 24"
116+
fill="none"
117+
stroke="currentColor"
118+
strokeWidth="2"
119+
>
52120
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
53121
<circle cx="12" cy="12" r="3"></circle>
54122
</svg>
55123
</EyeIcon>
56124
</PasswordInput>
57-
125+
58126
<RememberSection>
59127
<CheckboxLabel>
60128
<Checkbox type="checkbox" />
61129
로그인 상태 유지
62130
</CheckboxLabel>
63-
131+
64132
<ForgotPassword href="#">비밀번호 찾기</ForgotPassword>
65133
</RememberSection>
66-
134+
67135
<LoginButton type="submit">로그인</LoginButton>
68136
</Form>
69-
137+
70138
<RegisterSection>
71139
<span>계정이 없으신가요?</span>
72-
<RegisterLink onClick={() => navigate('/signup')}>회원가입</RegisterLink>
140+
<RegisterLink onClick={() => navigate("/signup")}>
141+
회원가입
142+
</RegisterLink>
73143
</RegisterSection>
74144
</FormSection>
75145
</ContentContainer>

0 commit comments

Comments
 (0)