diff --git a/src/entities/user/api/user-api.server.ts b/src/entities/user/api/user-api.server.ts index 4d9077bd..0b82ab9a 100644 --- a/src/entities/user/api/user-api.server.ts +++ b/src/entities/user/api/user-api.server.ts @@ -1,5 +1,3 @@ -import { cache } from "react"; - import { cookies } from "next/headers"; import { fetch as apiFetch } from "@/shared/api/server"; @@ -14,7 +12,7 @@ interface UserApiResponse { isOwner: boolean; } -export const getUserProfileServer = cache(async (targetUserId: number) => { +export const getUserProfileServer = async (targetUserId: number) => { const cookieStore = await cookies(); const accessToken = cookieStore.get("accessToken")?.value; @@ -45,4 +43,4 @@ export const getUserProfileServer = cache(async (targetUserId: number) => { } catch { return null; } -}); +}; diff --git a/src/entities/user/index.ts b/src/entities/user/index.ts index a8324991..6bbdaa32 100644 --- a/src/entities/user/index.ts +++ b/src/entities/user/index.ts @@ -3,4 +3,9 @@ export type { UserProfileType } from "./model/types"; export type { UserBasicInfoResponseType } from "./model/types"; export { DASHBOARD_TABS } from "./model/dashboard-tabs.config"; export type { TabIdType, TabConfig } from "./model/types"; -export { getUserProfile, updateUserProfileImage, updateUserName } from "./api/user-api"; +export { + getUserProfile, + getUserBasic, + updateUserProfileImage, + updateUserName, +} from "./api/user-api"; diff --git a/src/features/user/api/use-my-profile.ts b/src/features/user/api/use-my-profile.ts index 701aac4f..d6906437 100644 --- a/src/features/user/api/use-my-profile.ts +++ b/src/features/user/api/use-my-profile.ts @@ -1,11 +1,13 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { getUserProfile, updateUserProfileImage, updateUserName } from "@/entities/user"; -import type { UserProfileType } from "@/entities/user"; // 타입 위치에 맞게 수정 +import type { UserProfileType } from "@/entities/user"; import { showToast } from "@/shared/lib/utils/toast/show-toast"; export const userKeys = { - profile: (userId: number) => ["user", "profile", userId] as const, + all: ["user"] as const, + basic: () => [...userKeys.all, "basic"] as const, + profile: (userId: number) => [...userKeys.all, "profile", userId] as const, }; export function useUserProfile(userId: number, initialData?: UserProfileType) { diff --git a/src/features/user/api/use-user-basic.ts b/src/features/user/api/use-user-basic.ts new file mode 100644 index 00000000..d00a6508 --- /dev/null +++ b/src/features/user/api/use-user-basic.ts @@ -0,0 +1,24 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; + +import { getUserBasic } from "@/entities/user"; +import { userKeys } from "@/features/user/api/use-my-profile"; + +async function fetchUserBasic() { + try { + return await getUserBasic(); + } catch { + return null; + } +} + +export function useUserBasic() { + return useQuery({ + queryKey: userKeys.basic(), + queryFn: fetchUserBasic, + retry: false, + staleTime: 1000 * 60 * 5, + refetchOnWindowFocus: false, + }); +} diff --git a/src/widgets/header/header.tsx b/src/widgets/header/header.tsx index 2adc1b9f..d6bda7fc 100644 --- a/src/widgets/header/header.tsx +++ b/src/widgets/header/header.tsx @@ -1,23 +1,14 @@ -import { cookies } from "next/headers"; +import { Suspense } from "react"; + import Image from "next/image"; import Link from "next/link"; -import { LogIn } from "lucide-react"; - import Logo from "@/shared/assets/icons/windfall.svg"; -import { ROUTES } from "@/shared/config/routes"; import { Container } from "@/shared/ui"; -import Button from "@/shared/ui/button/button"; -import HeaderActions from "@/widgets/header/ui/header-actions"; +import HeaderAuthSlot from "@/widgets/header/ui/header-auth-slot"; import HeaderSearch from "@/widgets/header/ui/header-search"; -export async function Header() { - const cookieStore = await cookies(); - const userId = cookieStore.get("userId")?.value; - - const numericUserId = Number(userId); - const hasValidUserId = Number.isInteger(numericUserId) && numericUserId > 0; - +export function Header() { return (
@@ -28,18 +19,11 @@ export async function Header() { - + }> + + - {hasValidUserId ? ( - - ) : ( - - )} +
); diff --git a/src/widgets/header/ui/header-actions.tsx b/src/widgets/header/ui/header-actions.tsx index 5b470cf0..bf759254 100644 --- a/src/widgets/header/ui/header-actions.tsx +++ b/src/widgets/header/ui/header-actions.tsx @@ -1,18 +1,20 @@ +"use client"; + import Link from "next/link"; import { Bell, Plus } from "lucide-react"; -import { getUserProfileServer } from "@/entities/user/api/user-api.server"; import { ROUTES } from "@/shared/config/routes"; import { Button } from "@/shared/ui"; import HeaderUserMenu from "@/widgets/header/ui/header-user-menu"; -export default async function HeaderActions({ userId }: { userId: number }) { - const profile = await getUserProfileServer(userId); - - const avatarUrl = profile?.avatarUrl; - const avatarAlt = profile?.name ?? "프로필"; - +export default function HeaderActions({ + avatarUrl, + avatarAlt, +}: { + avatarUrl?: string; + avatarAlt: string; +}) { return (
- +
); } diff --git a/src/widgets/header/ui/header-auth-slot.tsx b/src/widgets/header/ui/header-auth-slot.tsx new file mode 100644 index 00000000..61d7bdaa --- /dev/null +++ b/src/widgets/header/ui/header-auth-slot.tsx @@ -0,0 +1,36 @@ +"use client"; + +import Link from "next/link"; + +import { LogIn } from "lucide-react"; + +import { useUserBasic } from "@/features/user/api/use-user-basic"; +import { ROUTES } from "@/shared/config/routes"; +import Button from "@/shared/ui/button/button"; +import HeaderActions from "@/widgets/header/ui/header-actions"; + +export default function HeaderAuthSlot() { + const { data, isPending } = useUserBasic(); + + if (isPending) { + return
; + } + + if (!data) { + return ( + + ); + } + + return ( + + ); +} diff --git a/src/widgets/header/ui/header-user-menu.tsx b/src/widgets/header/ui/header-user-menu.tsx index 9c1c79c0..f4712887 100644 --- a/src/widgets/header/ui/header-user-menu.tsx +++ b/src/widgets/header/ui/header-user-menu.tsx @@ -3,9 +3,11 @@ import Link from "next/link"; import { useRouter } from "next/navigation"; +import { useQueryClient } from "@tanstack/react-query"; import { LogOut, Moon, Sun, User } from "lucide-react"; import { useTheme } from "next-themes"; +import { userKeys } from "@/features/user/api/use-my-profile"; import { ROUTES } from "@/shared/config/routes"; import { showToast } from "@/shared/lib/utils/toast/show-toast"; import { @@ -26,6 +28,7 @@ interface HeaderUserMenuProps { export default function HeaderUserMenu({ avatarUrl, avatarAlt }: HeaderUserMenuProps) { const router = useRouter(); + const queryClient = useQueryClient(); const { resolvedTheme, setTheme } = useTheme(); const isDarkMode = resolvedTheme === "dark"; const themeLabel = isDarkMode ? "라이트 모드" : "다크 모드"; @@ -40,6 +43,7 @@ export default function HeaderUserMenu({ avatarUrl, avatarAlt }: HeaderUserMenuP } catch { showToast.error("로그아웃에 실패했습니다. 잠시 후 다시 시도해주세요."); } finally { + queryClient.removeQueries({ queryKey: userKeys.all, exact: false }); router.refresh(); router.push(ROUTES.login); }