Skip to content
Merged
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
5 changes: 3 additions & 2 deletions next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ const nextConfig: NextConfig = {
{ protocol: "https", hostname: "static.toss.im" },
{ protocol: "https", hostname: "windfall-bucket.s3.ap-northeast-2.amazonaws.com" },
{ protocol: "https", hostname: "wind-fall.store" },
{ protocol: "http", hostname: "img1.kakaocdn.net" },
{ protocol: "http", hostname: "phinf.pstatic.net" },
{ protocol: "https", hostname: "phinf.pstatic.net" },
{ protocol: "http", hostname: "ssl.pstatic.net" },
{ protocol: "https", hostname: "ssl.pstatic.net" },
{ protocol: "http", hostname: "lh3.googleusercontent.com" },
{ protocol: "https", hostname: "phinf.pstatic.net" },
{ protocol: "https", hostname: "ssl.pstatic.net" },
{ protocol: "https", hostname: "lh3.googleusercontent.com" },
],
},
Expand Down
5 changes: 3 additions & 2 deletions src/app/(protected)/users/[userId]/[tab]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { getUserProfileServer } from "@/entities/user/api/user-api.server";
import { UserTabContent } from "@/screens/user";

interface PageProps {
Expand All @@ -9,8 +10,8 @@ interface PageProps {

export default async function Page({ params }: PageProps) {
const { userId, tab } = await params;

const targetUserId = Number(userId);
const profile = await getUserProfileServer(targetUserId);

return <UserTabContent tabId={tab} targetUserId={targetUserId} />;
return <UserTabContent tabId={tab} targetUserId={targetUserId} initialData={profile} />;
}
2 changes: 1 addition & 1 deletion src/app/(protected)/users/[userId]/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export default async function UserLayout({ children, params }: LayoutProps) {
return (
<HydrationBoundary state={dehydrate(queryClient)}>
<main className="bg-background mx-auto flex min-h-screen w-full max-w-7xl flex-col items-center gap-4 px-4 py-6 2xl:px-0">
<UserDashboardHeader targetUserId={targetUserId} />
<UserDashboardHeader targetUserId={targetUserId} initialData={profile} />
<div className="w-full">{children}</div>
</main>
</HydrationBoundary>
Expand Down
6 changes: 4 additions & 2 deletions src/entities/user/api/user-api.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { fetch as apiFetch } from "@/shared/api/server";
import { API_ENDPOINTS } from "@/shared/config/endpoints";

interface UserApiResponse {
userId: number;
username: string;
email: string;
profileImage: string | null;
Expand All @@ -30,9 +31,10 @@ export const getUserProfileServer = async (targetUserId: number) => {

const { data } = response;

if (!data) return null;
if (!data) return undefined;

return {
userId: data.userId,
name: data.username,
email: data.email,
avatarUrl: data.profileImage || undefined,
Expand All @@ -41,6 +43,6 @@ export const getUserProfileServer = async (targetUserId: number) => {
isOwner: data.isOwner,
};
} catch {
return null;
return undefined;
}
};
11 changes: 9 additions & 2 deletions src/screens/user/ui/user-tab-content.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import dynamic from "next/dynamic";
import { notFound } from "next/navigation";

import { DASHBOARD_TABS, type TabIdType } from "@/entities/user";
import { UserProfileType } from "@/entities/user";
import { TabLabelHeader } from "@/features/user";
import { useUserProfile } from "@/features/user/api/use-my-profile";
import { CommonItemTabSkeleton, ReviewTabSkeleton, CalendarTabSkeleton } from "@/widgets/user";
Expand Down Expand Up @@ -68,9 +69,10 @@ function isValidTabId(id: string): id is TabIdType {
interface UserTabContentProps {
tabId: string;
targetUserId: number;
initialData: UserProfileType | undefined;
}

export function UserTabContent({ tabId, targetUserId }: UserTabContentProps) {
export function UserTabContent({ tabId, targetUserId, initialData }: UserTabContentProps) {
if (!isValidTabId(tabId)) notFound();

const currentTabId = tabId;
Expand All @@ -79,7 +81,12 @@ export function UserTabContent({ tabId, targetUserId }: UserTabContentProps) {
const currentTabConfig = DASHBOARD_TABS.find((t) => t.id === currentTabId);
if (!currentTabConfig) notFound();

const { data: profile, isLoading, isFetching, isPending } = useUserProfile(targetUserId);
const {
data: profile,
isLoading,
isFetching,
isPending,
} = useUserProfile(targetUserId, initialData);

// 핵심: 프로필/권한이 "확정"되기 전에는 비공개 판정을 하지 말고
// 서버/클라 동일하게 스켈레톤을 렌더해서 트리를 고정한다.
Expand Down
12 changes: 9 additions & 3 deletions src/widgets/user/ui/user-dashboard-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

import { useParams } from "next/navigation";

import { DASHBOARD_TABS } from "@/entities/user";
import { DASHBOARD_TABS, UserProfileType } from "@/entities/user";
import { ActivityTabs, UserProfileCard } from "@/features/user";
import { useUserProfile } from "@/features/user/api/use-my-profile";

export function UserDashboardHeader({ targetUserId }: { targetUserId: number }) {
interface UserDashboardHeaderProps {
targetUserId: number;
initialData?: UserProfileType; // initial user profile data used before fetching
}

export function UserDashboardHeader({ targetUserId, initialData }: UserDashboardHeaderProps) {
const params = useParams();
const activeTab = (params.tab as string) || "calendar";

const { data: profile, isLoading } = useUserProfile(targetUserId);
const { data: profile, isLoading } = useUserProfile(targetUserId, initialData);

if (isLoading) {
return (
<div className="flex flex-col gap-4" data-testid="user-dashboard-header-skeleton">
Expand Down