Skip to content
Open
Show file tree
Hide file tree
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
38 changes: 22 additions & 16 deletions src/components/pages/LoginPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,35 @@ import Button from "../Button";
import HorizontalRule from "../HorizontalRule";
import Link from "next/link";
import styles from "./LoginPage.module.css";
import { useRouter } from "next/navigation";
import { authService } from "@/lib/authService";

function LoginPage() {
const [values, setValues] = useState({
email: "",
password: "",
});
const [values, setValues] = useState({ email: "", password: "" });
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const router = useRouter();

function handleChange(e) {
const { name, value } = e.target;

setValues((prevValues) => ({
...prevValues,
[name]: value,
}));
setValues((prev) => ({ ...prev, [name]: value }));
}

async function handleSubmit(e) {
e.preventDefault();
// TODO: 로그인 처리
// 1. fetch 를 사용하여 로그인 요청을 보냅니다.
// 2. 성공 시 Application 에 쿠키 내 토큰 저장 여부를 확인합니다.
// 3. 로딩 상태를 만들고 로딩중일 때는 로그인 버튼을 비활성화 합니다.
// 4. 추가로 로딩중일 때는 로그인 버튼텍스트를 "로그인 중..."으로 변경합니다.
// 5. 에러 상태를 만들고 로그인 요청이 실패 시 에러 메시지를 로그인버튼 바로 위에 표시합니다.
setError(null);
setLoading(true);
try {
await authService.login(values.email, values.password);
router.push("/me");
} catch (err) {
setError("로그인에 실패했습니다.");
} finally {
setLoading(false);
}
}


return (
<>
<h1 className={styles.Heading}>로그인</h1>
Expand Down Expand Up @@ -61,7 +64,10 @@ function LoginPage() {
value={values.password}
onChange={handleChange}
/>
<Button className={styles.Button}>로그인</Button>
{error && <div className={styles.Error}>{error}</div>}
<Button className={styles.Button} disabled={loading}>
{loading ? "로그인 중..." : "로그인"}
</Button>
<HorizontalRule className={styles.HorizontalRule}>또는</HorizontalRule>
<Button
className={styles.GoogleButton}
Expand Down
27 changes: 3 additions & 24 deletions src/components/pages/MyPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import styles from "./MyPage.module.css";
import { userService } from "@/lib/userService";

function MyPage() {
const [user, setUser] = useState(null);
Expand All @@ -12,18 +13,7 @@ function MyPage() {

async function getMe() {
try {
const response = await fetch(
"https://learn.codeit.kr/api/link-service/users/me",
{
credentials: "include",
},
);

if (!response.ok) {
throw new Error("사용자 정보를 가져오는데 실패했습니다.");
}

const userData = await response.json();
const userData = await userService.getMe();
setUser(userData);
} catch (error) {
console.error("사용자 정보 가져오기 실패:", error);
Expand All @@ -36,18 +26,7 @@ function MyPage() {

async function getMyLinks() {
try {
const response = await fetch(
"https://learn.codeit.kr/api/link-service/users/me/links",
{
credentials: "include",
},
);

if (!response.ok) {
throw new Error("링크 정보를 가져오는데 실패했습니다.");
}

const linksData = await response.json();
const linksData = await userService.getMyLinks();
setLinks(linksData);
} catch (error) {
console.error("링크 정보 가져오기 실패:", error);
Expand Down
35 changes: 7 additions & 28 deletions src/components/pages/RegisterPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Button from "../Button";
import Link from "next/link";
import HorizontalRule from "../HorizontalRule";
import styles from "./RegisterPage.module.css";
import { authService } from "@/lib/authService";

function RegisterPage() {
const [values, setValues] = useState({
Expand Down Expand Up @@ -36,40 +37,18 @@ function RegisterPage() {
return;
}

setLoading(true);
setError(null);
try {
setLoading(true);
setError(null);

const response = await fetch(
"https://learn.codeit.kr/api/link-service/users",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
name: values.name,
email: values.email,
password: values.password,
}),
},
);

if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.message || "회원가입에 실패했습니다.");
}

// 회원가입 성공 후 처리
alert("회원가입에 성공했습니다.");
await authService.register(values.name, values.email, values.password);
router.push("/login");
} catch (error) {
setError(error.message || "회원가입에 실패했습니다.");
} catch (err) {
setError("회원가입에 실패했습니다.");
} finally {
setLoading(false);
}
}

return (
<>
<h1 className={styles.Heading}>회원가입</h1>
Expand Down
63 changes: 0 additions & 63 deletions src/lib/auth.js

This file was deleted.

17 changes: 17 additions & 0 deletions src/lib/authService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { defaultFetch, cookieFetch } from "@/lib/fetchClient";

export const authService = {
// 쿠키 인증을 사용하는 로그인
login: (email, password) =>
cookieFetch("/auth/login", {
method: "POST",
body: JSON.stringify({ email, password }),
}),

// 회원가입
register: (name, email, password) =>
defaultFetch("/users", {
method: "POST",
body: JSON.stringify({ name, email, password }),
}),
};
75 changes: 75 additions & 0 deletions src/lib/fetchClient.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* 기본 fetch 클라이언트 - 인증이 필요 없는 일반 요청용
*/
export const defaultFetch = async (url, options = {}) => {
const baseURL = process.env.NEXT_PUBLIC_API_URL;
const defaultOptions = {
headers: {
"Content-Type": "application/json",
},
// Next.js 기본 캐싱 활성화
cache: "force-cache",
};

const mergedOptions = {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...options.headers,
},
};

const response = await fetch(`${baseURL}${url}`, mergedOptions);

if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}

// 응답 본문이 있는지 확인
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
return response.json();
}

// 본문이 없거나 JSON이 아닌 경우 응답 객체 자체 반환
return { status: response.status, ok: response.ok };
};

/**
* 쿠키 인증 fetch 클라이언트
*/
export const cookieFetch = async (url, options = {}) => {
const baseURL = process.env.NEXT_PUBLIC_API_URL;
const defaultOptions = {
credentials: "include",
cache: "no-store",
headers: {
"Content-Type": "application/json",
},
};

const mergedOptions = {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...options.headers,
},
};

const response = await fetch(`${baseURL}${url}`, mergedOptions);

if (!response.ok) {
throw new Error(`API error: ${response.status}`);
}

// 응답 본문이 있는지 확인
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
return response.json();
}

// 본문이 없거나 JSON이 아닌 경우 응답 객체 자체 반환
return { status: response.status, ok: response.ok };
};
9 changes: 9 additions & 0 deletions src/lib/userService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { cookieFetch } from "@/lib/fetchClient";

export const userService = {
// 사용자 정보 요청
getMe: () => cookieFetch("/users/me", { method: "GET" }),

// 사용자 링크 요청
getMyLinks: () => cookieFetch("/users/me/links", { method: "GET" }),
};