Skip to content

Commit 2951fe7

Browse files
authored
Merge pull request #48 from FE9-2/feat/gnb
feat: gnb ๋ฐ ๊ด€๋ จ ํŽ˜์ด์ง€, ํ›… ์ถ”๊ฐ€
2 parents 1ef52cd + 358efa5 commit 2951fe7

File tree

17 files changed

+411
-127
lines changed

17 files changed

+411
-127
lines changed

โ€Žsrc/app/(home)/page.tsxโ€Ž

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { useEffect, useState, useRef } from "react";
44
import Link from "next/link";
55
import Image from "next/image";
66

7+
import Footer from "../components/layout/Footer";
8+
79
export default function Home() {
810
const [visibleSections, setVisibleSections] = useState(new Set<string>());
911
const observer = useRef<IntersectionObserver | null>(null);
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"use client";
2+
3+
export default function AlbaList() {
4+
return <div>AlbaList</div>;
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"use client";
2+
3+
export default function AlbaTalk() {
4+
return <div>AlbaTalk</div>;
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"use client";
2+
3+
export default function MyAlbaForm() {
4+
return <div>MyAlbaForm</div>;
5+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"use client";
2+
3+
export default function Mypage() {
4+
return <div>Mypage</div>;
5+
}
Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,50 @@
11
import { AxiosError } from "axios";
22
import { cookies } from "next/headers";
3-
import { NextRequest, NextResponse } from "next/server";
4-
3+
import { NextResponse } from "next/server";
54
import apiClient from "@/lib/apiClient";
65

7-
export const POST = async (request: NextRequest): Promise<NextResponse> => {
6+
export const POST = async (): Promise<NextResponse> => {
87
try {
9-
const { refreshToken } = await request.json();
10-
const response = await apiClient.post("/auth/refresh", { refreshToken });
11-
const { accessToken } = response.data;
8+
const refreshToken = cookies().get("refreshToken")?.value;
9+
// ํ† ํฐ ๊ฐฑ์‹ 
10+
const refreshResponse = await apiClient.post("/auth/refresh", { refreshToken });
11+
const { accessToken } = refreshResponse.data;
12+
13+
if (!accessToken) {
14+
return NextResponse.json({ message: "ํ† ํฐ ๊ฐฑ์‹  ์‹คํŒจ" }, { status: 401 });
15+
}
16+
17+
// ์ƒˆ๋กœ์šด accessToken ์ฟ ํ‚ค ์„ค์ •
18+
cookies().set("accessToken", accessToken, {
19+
httpOnly: true,
20+
secure: process.env.NODE_ENV === "production",
21+
sameSite: "strict",
22+
maxAge: 60 * 60 * 30, // 30๋ถ„
23+
path: "/",
24+
});
25+
26+
// ์ƒˆ๋กœ์šด accessToken์œผ๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ์กฐํšŒ
27+
const userResponse = await apiClient.get("/users/me", {
28+
headers: {
29+
Authorization: `Bearer ${accessToken}`,
30+
},
31+
});
1232

13-
if (accessToken) {
14-
cookies().set("accessToken", accessToken, {
15-
httpOnly: true,
16-
secure: process.env.NODE_ENV === "production",
17-
sameSite: "strict",
18-
maxAge: 60 * 60 * 30, // 30๋ถ„
19-
path: "/",
20-
});
33+
const user = userResponse.data;
2134

22-
return NextResponse.json({ accessToken }, { status: 200 });
35+
if (!user) {
36+
return NextResponse.json({ message: "์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค." }, { status: 404 });
2337
}
2438

25-
return NextResponse.json({ message: "๋กœ๊ทธ์ธ ์‹คํŒจ" }, { status: 401 });
39+
// user ์ •๋ณด ๋ฐ˜ํ™˜
40+
return NextResponse.json({ user }, { status: 200 });
2641
} catch (error: unknown) {
2742
if (error instanceof AxiosError) {
28-
console.error(error);
43+
console.error("Refresh token error:", error);
2944
if (error.response) {
3045
return NextResponse.json({ message: error.response.data.message }, { status: error.response.status });
3146
}
3247
}
33-
return NextResponse.json({ message: "๋กœ๊ทธ์ธ ์‹คํŒจ" }, { status: 500 });
48+
return NextResponse.json({ message: "ํ† ํฐ ๊ฐฑ์‹  ์‹คํŒจ" }, { status: 500 });
3449
}
3550
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { AxiosError } from "axios";
2+
import { cookies } from "next/headers";
3+
import { NextResponse } from "next/server";
4+
import apiClient from "@/lib/apiClient";
5+
6+
export async function GET() {
7+
try {
8+
const accessToken = cookies().get("accessToken")?.value;
9+
10+
if (!accessToken) {
11+
return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
12+
}
13+
14+
const response = await apiClient.get("/users/me", {
15+
headers: {
16+
Authorization: `Bearer ${accessToken}`,
17+
},
18+
});
19+
20+
const user = response.data;
21+
22+
if (!user) {
23+
return NextResponse.json({ message: "User not found" }, { status: 404 });
24+
}
25+
26+
return NextResponse.json(user);
27+
} catch (error: unknown) {
28+
if (error instanceof AxiosError) {
29+
console.error("GET /api/users/me error:", error);
30+
if (error.response) {
31+
return NextResponse.json({ message: error.response.data.message }, { status: error.response.status });
32+
}
33+
}
34+
return NextResponse.json({ message: "Internal Server Error" }, { status: 500 });
35+
}
36+
}

โ€Žsrc/app/components/Header.tsxโ€Ž

Lines changed: 0 additions & 70 deletions
This file was deleted.
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
"use client";
2+
3+
import Image from "next/image";
4+
import Link from "next/link";
5+
import { usePathname } from "next/navigation";
6+
import { cn } from "@/lib/tailwindUtil";
7+
import { useState, useEffect } from "react";
8+
import { useAuth } from "@/hooks/useAuth";
9+
10+
export default function Header() {
11+
const [isSideMenuOpen, setIsSideMenuOpen] = useState(false);
12+
const { user, logout, refresh, isLoading } = useAuth();
13+
const pathname = usePathname();
14+
const [isFirstRefresh, setIsFirstRefresh] = useState(true);
15+
16+
// ์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ์‹œ ํ•œ ๋ฒˆ๋งŒ refresh ์‹œ๋„
17+
useEffect(() => {
18+
if (!user && isFirstRefresh) {
19+
refresh();
20+
setIsFirstRefresh(false);
21+
}
22+
}, [user, refresh, isFirstRefresh]);
23+
24+
const handleLogout = () => {
25+
try {
26+
logout();
27+
setIsSideMenuOpen(false);
28+
} catch (error) {
29+
console.error("๋กœ๊ทธ์•„์›ƒ ์‹คํŒจ:", error);
30+
}
31+
};
32+
33+
const getLinkClassName = (path: string) => {
34+
return cn(
35+
"font-medium transition-colors h-16 flex items-center",
36+
"hover:text-lime-900",
37+
pathname === path
38+
? "text-lime-900 font-bold text-base sm:text-base md:text-lg"
39+
: "text-lime-700 text-sm sm:text-base"
40+
);
41+
};
42+
43+
if (isLoading) {
44+
return (
45+
<header className="bg-lime-100 shadow-md">
46+
<div className="container mx-auto px-4">
47+
<nav className="flex h-16 items-center justify-between">
48+
<div className="flex items-center">
49+
<Link href="/" className="text-xl text-white hover:text-blue-100">
50+
<Image
51+
src="/logo.svg"
52+
alt="Work Root Logo"
53+
width={200}
54+
height={60}
55+
className="w-32 hover:opacity-90 sm:w-40 md:w-[200px]"
56+
/>
57+
</Link>
58+
59+
<div className="ml-4 flex h-16 space-x-3 sm:ml-6 sm:space-x-4 md:ml-10 md:space-x-6">
60+
<Link href="/albaList" className={getLinkClassName("/albaList")}>
61+
์•Œ๋ฐ” ๋ชฉ๋ก
62+
</Link>
63+
<Link href="/albaTalk" className={getLinkClassName("/albaTalk")}>
64+
์•Œ๋ฐ” ํ† ํฌ
65+
</Link>
66+
</div>
67+
</div>
68+
69+
<div className="hidden space-x-4 md:flex">
70+
<div className="h-8 w-20 animate-pulse rounded-lg bg-lime-200" />
71+
<div className="h-8 w-20 animate-pulse rounded-lg bg-lime-200" />
72+
</div>
73+
</nav>
74+
</div>
75+
</header>
76+
);
77+
}
78+
79+
return (
80+
<header className="bg-lime-100 shadow-md">
81+
<div className="container mx-auto px-4">
82+
<nav className="flex h-16 items-center justify-between">
83+
<div className="flex items-center">
84+
<Link href="/" className="text-xl text-white hover:text-blue-100">
85+
<Image
86+
src="/logo.svg"
87+
alt="Work Root Logo"
88+
width={200}
89+
height={60}
90+
className="w-32 hover:opacity-90 sm:w-40 md:w-[200px]"
91+
/>
92+
</Link>
93+
94+
<div className="ml-4 flex h-16 space-x-3 sm:ml-6 sm:space-x-4 md:ml-10 md:space-x-6">
95+
<Link href="/albaList" className={getLinkClassName("/albaList")}>
96+
์•Œ๋ฐ” ๋ชฉ๋ก
97+
</Link>
98+
<Link href="/albaTalk" className={getLinkClassName("/albaTalk")}>
99+
์•Œ๋ฐ” ํ† ํฌ
100+
</Link>
101+
{user && (
102+
<Link href="/myAlbaform" className={getLinkClassName("/myAlbaform")}>
103+
๋‚ด ์•Œ๋ฐ”ํผ
104+
</Link>
105+
)}
106+
</div>
107+
</div>
108+
109+
<ul className="flex items-center space-x-3 sm:space-x-4 md:space-x-6">
110+
{!user ? (
111+
<>
112+
<li className="flex items-center">
113+
<Link
114+
href="/login"
115+
className="rounded-lg border-2 border-lime-700 px-2 py-1 text-sm text-lime-700 transition-colors hover:bg-lime-700 hover:text-white sm:px-3 sm:py-1.5 sm:text-base md:px-4 md:py-2"
116+
>
117+
๋กœ๊ทธ์ธ
118+
</Link>
119+
</li>
120+
<li className="flex items-center">
121+
<Link
122+
href="/signup"
123+
className="rounded-lg bg-lime-700 px-2 py-1 text-sm font-semibold text-white transition-colors hover:bg-lime-800 sm:px-3 sm:py-1.5 sm:text-base md:px-4 md:py-2"
124+
>
125+
ํšŒ์›๊ฐ€์ž…
126+
</Link>
127+
</li>
128+
</>
129+
) : null}
130+
</ul>
131+
132+
{user && (
133+
<button onClick={() => setIsSideMenuOpen(true)} className="block" aria-label="๋ฉ”๋‰ด ์—ด๊ธฐ">
134+
<Image src="/icons/menu/menu-sm.svg" width={24} height={24} alt="๋ฉ”๋‰ด" className="block sm:hidden" />
135+
<Image src="/icons/menu/menu-md.svg" width={36} height={36} alt="๋ฉ”๋‰ด" className="hidden sm:block" />
136+
</button>
137+
)}
138+
</nav>
139+
</div>
140+
141+
{isSideMenuOpen && (
142+
<div className="bg-black fixed inset-0 z-40 bg-opacity-50" onClick={() => setIsSideMenuOpen(false)} />
143+
)}
144+
145+
<div
146+
className={cn(
147+
"fixed right-0 top-0 z-50 h-full w-64 transform bg-white shadow-lg transition-transform duration-300 ease-in-out",
148+
isSideMenuOpen ? "translate-x-0" : "translate-x-full"
149+
)}
150+
>
151+
<div className="flex flex-col p-6">
152+
<div className="mb-6 flex items-center justify-between">
153+
<span className="text-lg font-bold text-lime-700">๋ฉ”๋‰ด</span>
154+
<button onClick={() => setIsSideMenuOpen(false)} className="text-gray-500 hover:text-gray-700">
155+
โœ•
156+
</button>
157+
</div>
158+
159+
{user && (
160+
<>
161+
<Link
162+
href="/mypage"
163+
className="mb-4 rounded-lg bg-lime-50 px-4 py-3 text-lime-700 hover:bg-lime-100"
164+
onClick={() => setIsSideMenuOpen(false)}
165+
>
166+
๋งˆ์ดํŽ˜์ด์ง€
167+
</Link>
168+
<button
169+
onClick={handleLogout}
170+
className="rounded-lg border-2 border-lime-700 px-4 py-3 text-lime-700 hover:bg-lime-700 hover:text-white"
171+
>
172+
๋กœ๊ทธ์•„์›ƒ
173+
</button>
174+
</>
175+
)}
176+
</div>
177+
</div>
178+
</header>
179+
);
180+
}

0 commit comments

Comments
ย (0)