Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions src/app/detail/[postingId]/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import NotFoundErrorPage from "@/shared/error/ui/404";
export default function PostDetailNotFound() {
return <NotFoundErrorPage message="해당 게시글을 찾을 수 없습니다" />;
}
25 changes: 17 additions & 8 deletions src/app/detail/[postingId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getPostDetail } from "@/entities/post/api/getPostDetail.server";
import { getUser } from "@/entities/user/api/getUser.server";
import PostDetailPageClient from "@/widgets/postDetail/ui/DetailPage.client";
import type { PostDetail } from "@/entities/post/model/types/post";
import { handleError } from "@/shared/error/handleError";

export default async function Page({
params,
Expand All @@ -17,18 +18,26 @@ export default async function Page({
const id = Number(postingId);
const queryClient = new QueryClient();

await queryClient.prefetchQuery({
queryKey: ["postDetail", id],
queryFn: () => getPostDetail(id),
});
try {
await queryClient.fetchQuery({
queryKey: ["postDetail", id],
queryFn: () => getPostDetail(id),
});
} catch (e) {
handleError(e);
}

const post = queryClient.getQueryData<PostDetail>(["postDetail", id]);

if (post && post.sellerId) {
await queryClient.prefetchQuery({
queryKey: ["seller", post.sellerId],
queryFn: () => getUser(post.sellerId),
});
try {
await queryClient.prefetchQuery({
queryKey: ["seller", post.sellerId],
queryFn: () => getUser(post.sellerId),
});
} catch (e) {
handleError(e);
}
}

return (
Expand Down
11 changes: 11 additions & 0 deletions src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use client";
import ServerErrorPage from "@/shared/error/ui/500";
export default function GlobalError({ error }: { error: Error }) {
return (
<html>
<body>
<ServerErrorPage message={error.message} />
</body>
</html>
);
}
23 changes: 18 additions & 5 deletions src/app/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,28 @@
"use client";

import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { LoginForm } from "@/features/auth/ui/LoginForm/LoginForm";
import { Modal } from "@/shared/ui/Modal/Modal";
import { useRouter } from "next/navigation";
import { useRouter, useSearchParams } from "next/navigation";
import { useAuthStore } from "@/features/auth/model/auth.store";

export default function SignUpPage() {
import { useChatStore } from "@/features/chat/model/chat.store";
export default function LoginPage() {
const router = useRouter();
const searchParams = useSearchParams();
const expired = searchParams.get("expired");

const [errorMessage, setErrorMessage] = useState<string | null>(null);
const { setIsLogined } = useAuthStore();
const { setIsLogined, logout } = useAuthStore();
const { unmount } = useChatStore();

useEffect(() => {
if (expired === "true") {
unmount();
logout();
setErrorMessage("세션이 만료되었습니다.\n다시 로그인해주세요.");
}
}, [expired]);

return (
<div className="flex min-h-[calc(100vh-70px)] flex-col items-center justify-center px-4 md:min-h-[calc(100vh-80px)] xl:min-h-[calc(100vh-100px)]">
<h1 className="mb-8 text-2xl font-bold text-white">로그인</h1>
Expand Down
21 changes: 13 additions & 8 deletions src/app/my/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,26 @@ import {
import { getMyPosts } from "@/entities/user/api/getMyPosts.server";
import { getMyProfile } from "@/entities/user/api/getMyProfile.server";
import MyPageClient from "@/widgets/mypage/ui/Client/MyPage.client";
import { handleError } from "@/shared/error/handleError";

const DEFAULT_TAB = "selling";

export default async function Page() {
const queryClient = new QueryClient();

await queryClient.prefetchQuery({
queryKey: ["userProfile"],
queryFn: getMyProfile,
});
try {
await queryClient.fetchQuery({
queryKey: ["userProfile"],
queryFn: getMyProfile,
});

await queryClient.prefetchQuery({
queryKey: ["myPosts", DEFAULT_TAB],
queryFn: () => getMyPosts(DEFAULT_TAB),
});
await queryClient.fetchQuery({
queryKey: ["myPosts", DEFAULT_TAB],
queryFn: () => getMyPosts(DEFAULT_TAB),
});
} catch (e) {
handleError(e);
}

return (
<HydrationBoundary state={dehydrate(queryClient)}>
Expand Down
5 changes: 5 additions & 0 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import NotFoundErrorPage from "@/shared/error/ui/404";

export default function GlobalNotFound() {
return <NotFoundErrorPage />;
}
27 changes: 16 additions & 11 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from "@tanstack/react-query";
import { getPosts } from "@/entities/post/api/getPosts.server";
import HomePageClient from "@/widgets/main/ui/Client/HomePage.client";
import { handleError } from "@/shared/error/handleError";

export default async function Page({
searchParams,
Expand All @@ -18,17 +19,21 @@ export default async function Page({

const queryClient = new QueryClient();

await queryClient.prefetchInfiniteQuery({
queryKey: ["posts", initialCategory, initialSort, initialKeyword],
queryFn: ({ pageParam = 1 }) =>
getPosts({
category: initialCategory,
sort: initialSort,
page: pageParam,
keyword: initialKeyword,
}),
initialPageParam: 1,
});
try {
await queryClient.fetchInfiniteQuery({
queryKey: ["posts", initialCategory, initialSort, initialKeyword],
queryFn: ({ pageParam = 1 }) =>
getPosts({
category: initialCategory,
sort: initialSort,
page: pageParam,
keyword: initialKeyword,
}),
initialPageParam: 1,
});
} catch (e) {
handleError(e);
}

return (
<HydrationBoundary state={dehydrate(queryClient)}>
Expand Down
18 changes: 17 additions & 1 deletion src/entities/chat/ui/ChattingRoom/ChattingRoom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { getPostDetail } from "@/entities/post/api/getPostDetail";
import { getUser } from "@/entities/user/api/getUser";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { createChattingRoom } from "@/features/chat/api/createChattingRoom";
import { handleError } from "@/shared/error/handleError";

export const ChattingRoom = ({
postingId,
Expand All @@ -36,31 +37,46 @@ export const ChattingRoom = ({
data: post,
isLoading: isPostLoading,
isError: isPostingError,
error: postingError,
} = useQuery({
queryKey: ["postDetail", postingId],
queryFn: () => getPostDetail(postingId),
});

if (isPostingError) {
handleError(postingError);
}

const {
data: otherUser,
isLoading: isOtherUserLoading,
isError: isOtherUserError,
error: otherUserError,
} = useQuery({
queryKey: ["otherUser", otherId],
queryFn: () => getUser(otherId),
});

if (isOtherUserError) {
handleError(otherUserError);
}

const {
messages,
pushMessageToCache,
fetchMoreMessages,
hasNextPage,
isMessagesFirstLoading,
isError: isChatMessagesError,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useChatMessages.ts가 반환하는 값의 타입에 isError가 없어서 빌드가 실패되는 것 같아요..!

error: chatMessagesError,
isMessagesLoading,
scrollContainerRef,
messagesEndRef,
} = useChatMessages(chatId);

if (isChatMessagesError) {
handleError(chatMessagesError);
}

const {
postStatus: currentPostStatus,
dealStatus: currentDealStatus,
Expand Down
9 changes: 5 additions & 4 deletions src/features/chat/ui/ChatList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { fetchChatList } from "../model/chat.api";
import type { Chat } from "@/entities/chat/model/types";
import { useQuery } from "@tanstack/react-query";
import { DealStatus } from "@/entities/chat/model/types";
import { handleError } from "@/shared/error/handleError";

interface ChatListProps {
onSelect: (info: {
Expand All @@ -29,12 +30,12 @@ const ChatList = ({ onSelect, tab = "all" }: ChatListProps) => {
queryFn: () => fetchChatList(role),
});

if (isLoading) {
return <p className="p-4 text-center text-white/70">불러오는 중...</p>;
if (isError) {
handleError(error);
}

if (isError) {
console.error("채팅 목록 불러오기 실패:", error);
if (isLoading) {
return <p className="p-4 text-center text-white/70">불러오는 중...</p>;
}

if (!chats?.length) {
Expand Down
20 changes: 16 additions & 4 deletions src/shared/api/fetcher.server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { cookies } from "next/headers";
import { redirect } from "next/navigation";
import { AuthorizationError, NotFoundError, ServerError } from "../error/error";

const BASE_URL = process.env.NEXT_PUBLIC_API_URL;

Expand Down Expand Up @@ -34,7 +34,9 @@ export async function serverFetch<T>(
const refreshToken = cookieStore.get("refreshToken")?.value;

if (!refreshToken) {
redirect("/login");
throw new AuthorizationError(
"세션이 만료되었습니다.\n다시 로그인 해주세요.",
);
}

const refreshRes = await fetch("/api/auth/refresh", {
Expand All @@ -59,13 +61,23 @@ export async function serverFetch<T>(
...restOptions,
});
} else {
redirect("/login");
throw new AuthorizationError(
"세션이 만료되었습니다.\n다시 로그인 해주세요.",
);
}
}

if (res.status === 404) {
throw new NotFoundError();
}

if (res.status >= 500) {
throw new ServerError();
}

if (!res.ok) {
const text = await res.text();
throw new Error(text || `API Error ${res.status}`);
throw new ServerError(text);
}

return res.json() as Promise<T>;
Expand Down
26 changes: 12 additions & 14 deletions src/shared/api/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useAuthStore } from "@/features/auth/model/auth.store";
import { useModalStore } from "@/shared/model/modal.store";
import { AuthorizationError, NotFoundError, ServerError } from "../error/error";

const BASE_URL = process.env.NEXT_PUBLIC_API_URL;

Expand All @@ -15,7 +15,6 @@ export async function apiFetch<T>(
const { headers, noAuth, useBaseUrl = true, ...restOptions } = options;

const { accessToken, setAccessToken, logout } = useAuthStore.getState();
const { openModal, closeModal } = useModalStore.getState();

const defaultHeaders: HeadersInit = {
"Content-Type": "application/json",
Expand All @@ -42,17 +41,9 @@ export async function apiFetch<T>(

if (!refreshed.ok) {
logout();

openModal("normal", {
message: "세션이 만료되었습니다. 다시 로그인 해주세요.",
buttonText: "확인",
onClick: () => {
closeModal();
location.replace("/login");
},
});

throw new Error("세션 만료");
throw new AuthorizationError(
"세션이 만료되었습니다.\n다시 로그인 해주세요.",
);
}

const { accessToken: newToken } = await refreshed.json();
Expand All @@ -69,6 +60,13 @@ export async function apiFetch<T>(
}

if (!res.ok) {
if (res.status === 404) {
throw new NotFoundError();
}

if (res.status >= 500) {
throw new ServerError();
}
let message = `API Error ${res.status}`;

try {
Expand All @@ -79,7 +77,7 @@ export async function apiFetch<T>(
if (text) message = text;
}

throw new Error(message);
throw new ServerError(message);
}

return res.json() as Promise<T>;
Expand Down
27 changes: 27 additions & 0 deletions src/shared/error/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export class BaseError extends Error {
constructor(
message: string,
public readonly status?: number,
) {
super(message);
this.name = this.constructor.name;
}
}

export class AuthorizationError extends BaseError {
constructor(message = "인증이 필요합니다.") {
super(message, 401);
}
}

export class NotFoundError extends BaseError {
constructor(message = "요청한 리소스를 찾을 수 없습니다.") {
super(message, 404);
}
}

export class ServerError extends BaseError {
constructor(message = "서버 오류가 발생했습니다.") {
super(message, 500);
}
}
Loading