Skip to content

Commit 9ff6e2e

Browse files
authored
Merge pull request #91 from part3-4team-Taskify/feature/Login
[Feat, Refactor] login: 로그인 요청, token 저장 기능 추가 / 공통 상수 폴더 추가
2 parents 10ec577 + 44d1fe1 commit 9ff6e2e

File tree

8 files changed

+123
-42
lines changed

8 files changed

+123
-42
lines changed

package-lock.json

Lines changed: 33 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"react": "^19.0.0",
1919
"react-datetime": "^3.3.1",
2020
"react-dom": "^19.0.0",
21-
"react-hook-form": "^7.54.2"
21+
"react-hook-form": "^7.54.2",
22+
"zustand": "^5.0.3"
2223
},
2324
"devDependencies": {
2425
"@eslint/eslintrc": "^3",

src/api/axiosInstance.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,13 @@ const axiosInstance = axios.create({
1212
axiosInstance.defaults.headers.common["Authorization"] =
1313
`Bearer ${process.env.NEXT_PUBLIC_API_TOKEN}`;
1414

15+
// 👉 요청 보낼 때마다 토큰 자동 추가
16+
axiosInstance.interceptors.request.use((config) => {
17+
const token = localStorage.getItem("accessToken");
18+
if (token) {
19+
config.headers.Authorization = `Bearer ${token}`;
20+
}
21+
return config;
22+
});
23+
1524
export default axiosInstance;

src/api/members.ts

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import axios from "axios";
1+
import axiosInstance from "./axiosInstance";
22
import { MemberType } from "@/types/users";
33

4-
interface ApiResponse {
4+
interface MembersResponse {
55
members: MemberType[]; // 실제 데이터
66
totalCount: number;
77
}
@@ -11,21 +11,11 @@ export const getMembers = async (dashboardId?: string | string[]) => {
1111
console.error("dashboardID가 없습니다.");
1212
return [];
1313
}
14-
15-
const token = process.env.NEXT_PUBLIC_API_TOKEN;
16-
const numericDashboardId =
14+
const dashboardIdNum =
1715
typeof dashboardId === "string" ? Number(dashboardId) : undefined;
1816

19-
const response = await axios.get<ApiResponse>(
20-
`${process.env.NEXT_PUBLIC_BASE_URL}13-4/members`,
21-
{
22-
params: {
23-
dashboardId: numericDashboardId,
24-
},
25-
headers: {
26-
Authorization: `Bearer ${token}`,
27-
},
28-
}
17+
const response = await axiosInstance.get<MembersResponse>(
18+
`https://sp-taskify-api.vercel.app/13-4/members?page=1&size=20&dashboardId=${dashboardIdNum}`
2919
);
3020

3121
return response.data.members || [];

src/api/user.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import axios from "axios";
1+
import axiosInstance from "./axiosInstance";
22

33
interface UserResponse {
44
id: number;
@@ -9,17 +9,7 @@ interface UserResponse {
99
updatedAt: string;
1010
}
1111

12-
export const getUserInfo = async () => {
13-
const token = process.env.NEXT_PUBLIC_API_TOKEN;
14-
15-
const response = await axios.get<UserResponse>(
16-
`https://sp-taskify-api.vercel.app/13-4/users/me`,
17-
{
18-
headers: {
19-
Authorization: `Bearer ${token}`,
20-
},
21-
}
22-
);
23-
24-
return response.data || [];
12+
export const getUserInfo = async ({ teamId }: { teamId: string }) => {
13+
const response = await axiosInstance.get<UserResponse>(`/${teamId}/users/me`);
14+
return response.data;
2515
};

src/constants/team.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const TEAM_ID = "13-4";

src/pages/login.tsx

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,70 @@
11
import { useState } from "react";
2-
import Input from "@/components/input/Input";
2+
import { useRouter } from "next/router";
3+
import { getUserInfo } from "@/api/user";
4+
import useUserStore from "@/store/useUserStore";
5+
import axiosInstance from "@/api/axiosInstance";
36
import Link from "next/link";
7+
import Input from "@/components/input/Input";
8+
import { TEAM_ID } from "@/constants/team";
49

510
export default function LoginPage() {
6-
const [email, setEmail] = useState("");
7-
const [password, setPassword] = useState("");
11+
const router = useRouter();
12+
const [values, setValues] = useState({
13+
email: "",
14+
password: "",
15+
});
16+
const isFormValid = Object.values(values).every((v) => v.trim() !== "");
17+
18+
const handleChange = (name: string) => (value: string) => {
19+
setValues((prev) => ({
20+
...prev,
21+
[name]: value,
22+
}));
23+
};
24+
25+
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
26+
e.preventDefault();
27+
const { email, password } = values;
28+
try {
29+
const response = await axiosInstance.post(`${TEAM_ID}/auth/login`, {
30+
email,
31+
password,
32+
});
33+
34+
const token = response.data.accessToken;
35+
localStorage.setItem("accessToken", token);
36+
37+
const userData = await getUserInfo({ teamId: TEAM_ID }); // 로그인 성공 후 사용자 정보 요청
38+
useUserStore.getState().setUser(userData); // Zustand에 저장
839

9-
const isFormValid = email.trim() !== "" && password.trim() !== "";
40+
router.push("/mydashboard");
41+
} catch (error) {
42+
console.error("로그인 실패:", error);
43+
alert("로그인에 실패했습니다.");
44+
}
45+
};
1046

1147
return (
1248
<div className="flex flex-col items-center justify-center h-screen bg-[var(--color-gray5)] py-10">
1349
<div className="text-center mb-[40px]">
1450
<img
1551
src="/svgs/main-logo.svg"
1652
alt="태스키파이 로고 이미지"
17-
className="w-[200px] h-[280px] relative"
53+
className="w-[200px] h-[280px]"
1854
/>
1955
<p className="font-20m text-black3">오늘도 만나서 반가워요!</p>
2056
</div>
2157

22-
<form className="flex flex-col w-[350px] md:w-[520px] gap-[20px] font-16r text-black3">
58+
<form
59+
onSubmit={handleSubmit}
60+
className="flex flex-col w-[350px] md:w-[520px] gap-[20px] font-16r text-black3"
61+
>
2362
<Input
2463
type="email"
2564
name="email"
2665
label="이메일"
2766
placeholder="이메일을 입력해 주세요"
28-
onChange={setEmail}
29-
pattern="^[\w.-]+@[\w.-]+\.\w{2,}$"
67+
onChange={handleChange("email")}
3068
invalidMessage="올바른 이메일 주소를 입력해 주세요"
3169
/>
3270

@@ -35,7 +73,7 @@ export default function LoginPage() {
3573
name="password"
3674
label="비밀번호"
3775
placeholder="비밀번호를 입력해 주세요"
38-
onChange={setPassword}
76+
onChange={handleChange("password")}
3977
pattern=".{8,}"
4078
invalidMessage="비밀번호는 8자 이상이어야 해요"
4179
/>

src/store/useUserStore.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { create } from "zustand";
2+
3+
interface UserInfo {
4+
id: number;
5+
email: string;
6+
nickname: string;
7+
profileImageUrl: string;
8+
}
9+
10+
interface UserState {
11+
user: UserInfo | null;
12+
setUser: (user: UserInfo) => void;
13+
clearUser: () => void;
14+
}
15+
16+
const useUserStore = create<UserState>((set) => ({
17+
user: null,
18+
setUser: (user) => set({ user }),
19+
clearUser: () => set({ user: null }),
20+
}));
21+
22+
export default useUserStore;

0 commit comments

Comments
 (0)