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
6 changes: 4 additions & 2 deletions src/app/error.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
"use client";

import ServerErrorPage from "@/shared/error/ui/500";

export default function GlobalError({ error }: { error: Error }) {
return (
<html>
<body>
<html lang="kr">
<body className="mx-auto max-w-[1200px] bg-[#1c1c22] pt-[70px] md:pt-20 xl:pt-[100px]">
<ServerErrorPage message={error.message} />
</body>
</html>
Expand Down
19 changes: 10 additions & 9 deletions src/app/my/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,16 @@ export default async function Page() {
const queryClient = new QueryClient();

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

await queryClient.fetchQuery({
queryKey: ["myPosts", DEFAULT_TAB],
queryFn: () => getMyPosts(DEFAULT_TAB),
});
await Promise.all([
queryClient.fetchQuery({
queryKey: ["userProfile"],
queryFn: getMyProfile,
}),
queryClient.fetchQuery({
queryKey: ["myPosts", DEFAULT_TAB],
queryFn: () => getMyPosts(DEFAULT_TAB),
}),
]);
} catch (e) {
handleError(e);
}
Expand Down
2 changes: 1 addition & 1 deletion src/features/editPost/lib/usePostEditModal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const usePostEditModal = (handlers?: {
handlers?.onFailure?.();

openModal("normal", {
message: "게시물 수정 중 오류가 발생했습니다. " + message,
message: "게시물 수정을 실패했습니다.",
buttonText: "확인",
onClick: () => closeModal(),
});
Expand Down
56 changes: 56 additions & 0 deletions src/features/editProfile/lib/useProfileEditModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
"use client";

import { useModalStore } from "@/shared/model/modal.store";

export const useEditProfileModal = (
userProfile?: {
userId: number;
nickname: string;
introduction?: string;
imageUrl?: string;
category?: string;
},
handlers?: {
onSuccess?: () => void;
onFailure?: () => void;
},
) => {
const { openModal, closeModal } = useModalStore();

const openEditProfileModal = () => {
if (!userProfile) return;

openModal("editProfile", {
...userProfile,
onClose: () => closeModal(),

onSave: async () => {
closeModal();

handlers?.onSuccess?.();

setTimeout(() => {
openModal("normal", {
message: "프로필이 성공적으로 수정되었습니다.",
buttonText: "확인",
onClick: () => closeModal(),
});
}, 100);
},

onError: () => {
closeModal();

handlers?.onFailure?.();

openModal("normal", {
message: "프로필 수정에 실패했습니다.",
buttonText: "확인",
onClick: () => closeModal(),
});
},
});
};

return { openEditProfileModal };
};
8 changes: 8 additions & 0 deletions src/features/like/lib/useLike.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { updateFavorite } from "../api/updateFavorite";
import { useModalStore } from "@/shared/model/modal.store";

export function useLike(postingId?: number) {
const queryClient = useQueryClient();
const { openModal, closeModal } = useModalStore();

const mutation = useMutation({
mutationFn: async (liked: boolean) => {
Expand Down Expand Up @@ -33,7 +35,13 @@ export function useLike(postingId?: number) {

onError: (err, newLiked, context) => {
if (!postingId || !context?.previousData) return;
openModal("normal", {
message: "좋아요 처리 중 오류가 발생했습니다.",
onClick: () => closeModal(),
});
queryClient.setQueryData(["postDetail", postingId], context.previousData);

console.error(err);
},
});

Expand Down
36 changes: 21 additions & 15 deletions src/shared/api/fetcher.server.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { headers } from "next/headers";
import { NotFoundError, ServerError, AuthorizationError } from "../error/error";
import {
NotFoundError,
ServerError,
AuthorizationError,
BaseError,
} from "../error/error";
import { getBaseUrl } from "./config";

export async function serverFetch<T>(
endpoint: string,
options: RequestInit,
options: RequestInit = {},
): Promise<T> {
const { headers: extraHeaders = {}, ...restOptions } = options;

Expand All @@ -22,28 +27,29 @@ export async function serverFetch<T>(
},
cache: "no-store",
...restOptions,
});

if (res.status === 401) {
throw new AuthorizationError(
"세션이 만료되었습니다.\n다시 로그인 해주세요.",
}).catch(() => {
throw new ServerError(
"네트워크 오류가 발생했습니다.\n인터넷 연결을 확인하고 다시 시도해주세요.",
res.status,
);
}
});

if (!res.ok) {
if (res.status === 404) throw new NotFoundError();
if (res.status >= 500) throw new ServerError();
let message = `요청 실패 (${res.status})`;

let message = `API Error ${res.status}`;
try {
const data = await res.json();
message = data.message ?? data.detail ?? message;
if (typeof data === "object" && data?.message) {
message = data.message;
}
} catch {
const text = await res.text();
if (text) message = text;
// 기본 메시지
}

throw new ServerError(message);
if (res.status === 401) throw new AuthorizationError();
if (res.status === 404) throw new NotFoundError();
if (res.status >= 500) throw new ServerError(message, res.status);
throw new BaseError(message, res.status);
}

return res.json() as Promise<T>;
Expand Down
39 changes: 25 additions & 14 deletions src/shared/api/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { AuthorizationError, NotFoundError, ServerError } from "../error/error";
import {
AuthorizationError,
NotFoundError,
ServerError,
BaseError,
} from "../error/error";

export async function apiFetch<T>(
endpoint: string,
Expand All @@ -16,26 +21,32 @@ export async function apiFetch<T>(
...restOptions,
});

if (res.status === 401 && !noAuth) {
throw new AuthorizationError(
"세션이 만료되었습니다.\n다시 로그인 해주세요.",
);
}

if (!res.ok) {
if (res.status === 404) throw new NotFoundError();
if (res.status >= 500) throw new ServerError();
let message = `요청 실패 (${res.status})`;

let message = `API Error ${res.status}`;
try {
const data = await res.json();
message = data.message ?? data.detail ?? message;
if (typeof data === "object" && data?.message) {
message = data.message;
}
} catch {
const text = await res.text();
if (text) message = text;
// 기본 메시지
}

if (res.status === 401 && !noAuth) {
throw new AuthorizationError(
"세션이 만료되었습니다.\n다시 로그인 해주세요.",
);
}

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

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

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

Expand All @@ -21,7 +21,7 @@ export class NotFoundError extends BaseError {
}

export class ServerError extends BaseError {
constructor(message = "서버 오류가 발생했습니다.") {
super(message, 500);
constructor(message = "서버 오류가 발생했습니다.", status: number) {
super(message, status);
}
}
15 changes: 12 additions & 3 deletions src/shared/error/ui/500.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@ export default function ServerErrorPage({ message }: { message?: string }) {

return (
<div className="flex flex-col items-center justify-center py-20 text-white">
<h1 className="text-3xl font-bold">서버 오류 발생</h1>
<p className="mt-4 text-lg opacity-80">
{message ?? "잠시 후 다시 시도해주세요."}
<h1 className="text-3xl font-bold">문제가 발생했어요</h1>

<p className="mt-4 text-center text-lg whitespace-pre-line opacity-80">
{message ??
"일시적인 문제로 페이지를 불러오지 못했어요.\n잠시 후 다시 시도해주세요."}
</p>

<button
onClick={() => window.location.reload()}
className="mt-8 rounded-md bg-[#383848] px-5 py-2 text-white hover:cursor-pointer hover:bg-[#4a4a5a]"
>
새로고침
</button>
</div>
);
}
Loading