Skip to content

Commit 0a2768f

Browse files
committed
Merge branch 'develop' of https://github.com/codeit9-temporary/linkbrary into feature/link-favorite
2 parents e5dd248 + d9ea590 commit 0a2768f

File tree

16 files changed

+346
-40
lines changed

16 files changed

+346
-40
lines changed

components/Auth/SnsLogin.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,21 @@ const SnsLogin = () => {
66
<div className="flex items-center justify-between bg-gray300 rounded-lg px-6 py-3 mt-8">
77
<span>소셜 로그인</span>
88
<div className="flex gap-4">
9-
<Link href="https://accounts.google.com/o/oauth2/v2/auth?scope=openid%20profile%20email&response_type=token&redirect_uri=http://localhost:3000/google&client_id=1079911783112-7rg5ecp9ia9lorm7pit0m2nb2ti1rpt0.apps.googleusercontent.com">
9+
<Link
10+
href={`https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile&response_type=code&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI_SIGN_IN}&client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}`}
11+
>
1012
<Image src="/icons/google.svg" width="42" height="42" alt="구글" />
1113
</Link>
12-
<Image
13-
src="/icons/kakaotalk.svg"
14-
width="42"
15-
height="42"
16-
alt="카카오톡"
17-
/>
14+
<Link
15+
href={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID}&redirect_uri=${process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI_SIGN_IN}&response_type=code`}
16+
>
17+
<Image
18+
src="/icons/kakaotalk.svg"
19+
width="42"
20+
height="42"
21+
alt="카카오톡"
22+
/>
23+
</Link>
1824
</div>
1925
</div>
2026
);

components/Auth/SnsPassword.tsx

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import Image from "next/image";
2+
import Link from "next/link";
3+
4+
const SnsLogin = () => {
5+
return (
6+
<div className="flex items-center justify-between bg-gray300 rounded-lg px-6 py-3 mt-8">
7+
<span>소셜 회원가입</span>
8+
<div className="flex gap-4">
9+
<Link
10+
href={`https://accounts.google.com/o/oauth2/v2/auth?scope=https://www.googleapis.com/auth/userinfo.profile&response_type=code&redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI_SIGN_UP}&client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}`}
11+
>
12+
<Image src="/icons/google.svg" width="42" height="42" alt="구글" />
13+
</Link>
14+
<Link
15+
href={`https://kauth.kakao.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_KAKAO_CLIENT_ID}&redirect_uri=${process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI_SIGN_UP}&response_type=code`}
16+
>
17+
<Image
18+
src="/icons/kakaotalk.svg"
19+
width="42"
20+
height="42"
21+
alt="카카오톡"
22+
/>
23+
</Link>
24+
</div>
25+
</div>
26+
);
27+
};
28+
29+
export default SnsLogin;

components/Dropdown.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,24 @@ const Dropdown = ({ items }: DropdownProps) => {
1717

1818
return (
1919
<div className="absolute top-[17px] right-0 flex flex-col gap-[2px] min-w-[100px] bg-white shadow-lg rounded">
20-
{items.map((item, index) =>
20+
{items.map((item) =>
2121
// href가 있으면 Link로 렌더링
2222
item.href ? (
23-
<Link key={index} href={item.href} className={buttonStyle}>
23+
<Link
24+
key={item.label}
25+
href={item.href}
26+
onClick={item.onClick}
27+
className={`${buttonStyle} text-center `}
28+
>
2429
{item.label}
2530
</Link>
2631
) : (
2732
// href가 없으면 버튼으로 렌더링
28-
<button key={index} onClick={item.onClick} className={buttonStyle}>
33+
<button
34+
key={item.label}
35+
onClick={item.onClick}
36+
className={buttonStyle}
37+
>
2938
{item.label}
3039
</button>
3140
)

components/HeaderMenu.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ const HeaderMenu = () => {
2424
},
2525
{
2626
label: "로그아웃",
27+
href: "/",
2728
onClick: logout,
2829
},
2930
];
@@ -58,7 +59,7 @@ const HeaderMenu = () => {
5859
즐겨찾기
5960
</Link>
6061
<div
61-
className="flex items-center gap-[6px] text-[14px] leading-[16.71px] font-normal"
62+
className="flex items-center gap-[6px] text-[14px] leading-[16.71px] font-normal cursor-pointer"
6263
onClick={() => setIsOpen(!isOpen)}
6364
>
6465
<Image src={Profile} width={28} height={28} alt="프로필" />

components/Link/LinkCard.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const LinkCard = ({ info }: LinkCardProps) => {
7878
alt="링크 미리보기"
7979
fill
8080
/>
81-
{/* isFavoritePage일 때만 즐겨찾기 버튼 렌더링 */}
81+
{/* 즐겨찾기 페이지가 아닐 때에는 즐겨찾기 버튼 렌더링x */}
8282
{!isFavoritePage && (
8383
<div
8484
onClick={handleFavoriteToggle}
@@ -114,12 +114,10 @@ const LinkCard = ({ info }: LinkCardProps) => {
114114
</div>
115115
)}
116116
</div>
117-
<div className="text-[black100] text-lg ">
117+
<div className="text-black100 y-[42px] line-clamp-2">
118118
{info.description || "설명"}
119119
</div>
120-
<div className="text-sm text-[black200]">
121-
{formattedDate || "2024.11.06"}
122-
</div>
120+
<div className="text-sm">{formattedDate || "2024.11.06"}</div>
123121
</section>
124122
</div>
125123
);

package-lock.json

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

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"dependencies": {
1212
"axios": "^1.7.7",
1313
"cookie": "^1.0.1",
14+
"jwt-decode": "^4.0.0",
1415
"next": "15.0.2",
1516
"react": "^18.2.0",
1617
"react-dom": "^18.2.0",

pages/api/auth/sign-in/google.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import axios from "axios";
2+
import axiosInstance from "@/lib/api/axiosInstanceApi";
3+
import { NextApiRequest, NextApiResponse } from "next";
4+
import { serialize } from "cookie";
5+
6+
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
7+
try {
8+
const { code } = req.query;
9+
if (!code) {
10+
return res.status(400).json({ message: "인증 코드가 없습니다." });
11+
}
12+
13+
const clientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
14+
const clientSecret = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET;
15+
const redirectUri =
16+
process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI_SIGN_IN ||
17+
"http://localhost:3000/";
18+
19+
if (!clientId || !clientSecret) {
20+
return res
21+
.status(500)
22+
.json({ message: "Google API 클라이언트 정보가 설정되지 않았습니다." });
23+
}
24+
25+
// 토큰 요청
26+
const tokenUrl = "https://oauth2.googleapis.com/token";
27+
const params = {
28+
code: code as string,
29+
client_id: clientId,
30+
client_secret: clientSecret,
31+
redirect_uri: redirectUri,
32+
grant_type: "authorization_code",
33+
};
34+
35+
const tokenResponse = await axios.post(tokenUrl, params);
36+
const { id_token } = tokenResponse.data;
37+
if (!id_token) {
38+
return res
39+
.status(401)
40+
.json({ message: "ID 토큰을 가져오지 못했습니다." });
41+
}
42+
43+
// 이미 회원인지 체크
44+
try {
45+
const loginResponse = await axiosInstance.post("/auth/sign-in/google", {
46+
token: id_token,
47+
redirectUri,
48+
});
49+
50+
const accessToken = loginResponse.data.access_token;
51+
if (accessToken) {
52+
res.setHeader(
53+
"Set-Cookie",
54+
serialize("accessToken", accessToken, {
55+
httpOnly: true,
56+
secure: process.env.NODE_ENV === "production",
57+
sameSite: "lax",
58+
maxAge: 60 * 60 * 24,
59+
path: "/",
60+
})
61+
);
62+
return res.redirect("http://localhost:3000");
63+
}
64+
} catch (loginError: any) {
65+
return res.redirect("/signup");
66+
}
67+
} catch (error: any) {
68+
console.error("Error:", error.response?.data || error.message);
69+
return res.status(500).json({
70+
message: "서버 오류",
71+
error: error.response?.data || error.message,
72+
});
73+
}
74+
};
75+
76+
export default handler;

pages/api/auth/sign-in/kakao.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import axiosInstance from "@/lib/api/axiosInstanceApi";
2+
import { NextApiRequest, NextApiResponse } from "next";
3+
import { serialize } from "cookie";
4+
5+
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
6+
try {
7+
const { code } = req.query;
8+
console.log(code);
9+
if (!code) {
10+
return res.status(400).json({ message: "인증 코드가 없습니다." });
11+
}
12+
13+
const redirectUri =
14+
process.env.NEXT_PUBLIC_KAKAO_REDIRECT_URI_SIGN_IN ||
15+
"http://localhost:3000/";
16+
17+
try {
18+
const loginResponse = await axiosInstance.post("/auth/sign-in/kakao", {
19+
token: code,
20+
redirectUri,
21+
});
22+
23+
const accessToken = loginResponse.data.access_token;
24+
if (accessToken) {
25+
res.setHeader(
26+
"Set-Cookie",
27+
serialize("accessToken", accessToken, {
28+
httpOnly: true,
29+
secure: process.env.NODE_ENV === "production",
30+
sameSite: "lax",
31+
maxAge: 60 * 60 * 24,
32+
path: "/",
33+
})
34+
);
35+
return res.redirect("http://localhost:3000");
36+
}
37+
} catch (loginError: any) {
38+
console.error(
39+
"로그인 실패:",
40+
loginError.response?.data || loginError.message
41+
);
42+
return res.redirect("signup");
43+
}
44+
} catch (error: any) {
45+
console.error("Error:", error.response?.data || error.message);
46+
return res.status(500).json({
47+
message: "서버 오류",
48+
error: error.response?.data || error.message,
49+
});
50+
}
51+
};
52+
53+
export default handler;

0 commit comments

Comments
 (0)