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
14 changes: 14 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 백엔드 API 베이스 URL (예: https://api.k-pullup.com/api/v1)
# 서버 실행 컨텍스트에서 사용. 미설정 시 SSR/CI 에서 undefined 요청이 발생할 수 있음.
NEXT_PUBLIC_BASE_URL=

# Kakao Maps / Kakao SDK 앱 키
NEXT_PUBLIC_APP_KEY=
NEXT_PUBLIC_REST_API_KEY=
NEXT_PUBLIC_REST_INTEGRITY_VALUE=

# Google Analytics 측정 ID (선택)
NEXT_PUBLIC_GOOGLE_ANALYTICS=

# Google AdSense 퍼블리셔 ID (선택)
NEXT_PUBLIC_GOOGLE_AD_CID=
1,092 changes: 28 additions & 1,064 deletions .pnp.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ const config: StorybookConfig = {
name: getAbsolutePath("@storybook/nextjs"),
options: {},
},
staticDirs: ["..\\public"],
staticDirs: ["../public"],
};
export default config;
320 changes: 320 additions & 0 deletions AUDIT-2026-09-08.md

Large diffs are not rendered by default.

249 changes: 249 additions & 0 deletions REFACTOR-SPEC-2026-09-08.md

Large diffs are not rendered by default.

62 changes: 0 additions & 62 deletions __tests__/lib/fetchData-http-error.property.test.ts

This file was deleted.

48 changes: 0 additions & 48 deletions __tests__/lib/fetchData-http-success.property.test.ts

This file was deleted.

86 changes: 0 additions & 86 deletions __tests__/lib/fetchData-response-body.property.test.ts

This file was deleted.

5 changes: 4 additions & 1 deletion app/(home)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ import { headers } from "next/headers";
import { type Device } from "../mypage/page";

const Home = async () => {
const [images, moment] = await Promise.all([newPictures(), getAllMoment()]);
const [images, moment] = await Promise.all([
newPictures().catch(() => [] as NewPictures[]),
getAllMoment().catch(() => []),
]);

const headersList = headers();
const userAgent = headersList.get("user-agent");
Expand Down
24 changes: 2 additions & 22 deletions app/admin/admin-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,7 @@ const AdminClient = ({ data }: { data: AllReportRes }) => {
setProcessingIds((prev) => new Set(prev).add(reportId));

try {
const response = await approveReport(reportId);

if (!response.ok) {
closeAlert();
openAlert({
title: "승인할 수 없습니다.",
description: "잠시 후 다시 시도해주세요.",
onClick: () => {},
});
return;
}
await approveReport(reportId);

closeAlert();
openAlert({
Expand Down Expand Up @@ -175,17 +165,7 @@ const AdminClient = ({ data }: { data: AllReportRes }) => {
setProcessingIds((prev) => new Set(prev).add(reportId));

try {
const response = await denyReport(reportId);

if (!response.ok) {
closeAlert();
openAlert({
title: "거절할 수 없습니다.",
description: "잠시 후 다시 시도해주세요.",
onClick: () => {},
});
return;
}
await denyReport(reportId);

closeAlert();
openAlert({
Expand Down
24 changes: 17 additions & 7 deletions app/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
import getAllReports from "@/lib/api/report/get-all-reports";
import AdminClient from "./admin-client";
import { cookies } from "next/headers";
import myInfo from "@/lib/api/user/myInfo";
import guardServerFetch from "@lib/server-fetch-guard";
import { cookies } from "next/headers";
import AdminClient from "./admin-client";

const AdminPage = async () => {
const cookieStore = cookies();
const decodeCookie = decodeURIComponent(cookieStore.toString());

const data = await getAllReports(decodeCookie);
const user = await myInfo(decodeCookie);

const noUser = !user || user.error;
const { status, data: user } = await guardServerFetch(() =>
myInfo(decodeCookie)
);

if (noUser) {
if (status !== "ok" || !user || !user.chulbong) {
return (
<h1 className="text-xl font-bold text-center mt-14">
접근 권한이 없습니다.
</h1>
);
}

const { data } = await guardServerFetch(() => getAllReports(decodeCookie));

if (!data) {
return (
<h1 className="text-xl font-bold text-center mt-14">
데이터를 불러올 수 없습니다.
</h1>
);
}

return <AdminClient data={data} />;
};

Expand Down
9 changes: 6 additions & 3 deletions app/mypage/bookmark/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import WarningText from "@common/warning-text";
import AuthError from "@layout/auth-error";
import NotFound from "@layout/not-found";
import getDeviceType from "@lib/get-device-type";
import guardServerFetch from "@lib/server-fetch-guard";
import BookmarkList from "@pages/mypage/bookmark/bookmark-list";
import { cookies, headers } from "next/headers";
import { type Device } from "../page";
Expand All @@ -19,9 +20,11 @@ const RankingPage = async () => {

const deviceType: Device = getDeviceType(userAgent as string);

const markers = await favorites(decodeCookie);
const { status, data: markers } = await guardServerFetch(() =>
favorites(decodeCookie)
);

if (markers.error === "No authorization token provided") {
if (status === "unauthorized") {
return (
<AuthError
headerTitle="즐겨찾기"
Expand All @@ -33,7 +36,7 @@ const RankingPage = async () => {
);
}

if (!markers.data || markers.data.length <= 0) {
if (!markers?.data || markers.data.length <= 0) {
return (
<NotFound
headerTitle="즐겨찾기"
Expand Down
12 changes: 0 additions & 12 deletions app/mypage/device-type.tsx

This file was deleted.

15 changes: 9 additions & 6 deletions app/mypage/locate/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Text from "@common/text";
import AuthError from "@layout/auth-error";
import NotFound from "@layout/not-found";
import getDeviceType from "@lib/get-device-type";
import guardServerFetch from "@lib/server-fetch-guard";
import RegisteredLocateList from "@pages/mypage/locate/registered-locate-list";
import { cookies, headers } from "next/headers";
import { type Device } from "../page";
Expand All @@ -19,18 +20,20 @@ const RankingPage = async () => {

const deviceType: Device = getDeviceType(userAgent as string);

const markers = await myRegisteredLocation({
pageParam: 1,
cookie: decodeCookie,
});
const { status, data: markers } = await guardServerFetch(() =>
myRegisteredLocation({
pageParam: 1,
cookie: decodeCookie,
})
);

if (markers.error === "No authorization token provided") {
if (status === "unauthorized") {
return (
<AuthError
headerTitle="내가 등록한 위치"
hasBackButton
errorTitle="로그인 후 철봉 위치를 등록해보세요."
returnUrl="mypage/locate"
returnUrl="/mypage/locate"
deviceType={deviceType}
/>
);
Expand Down
Loading
Loading