diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..6173321a6 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +node_modules +.next +Dockerfile +.dockerignore +.env* +apps/**/node_modules +packages/**/node_modules +apps/**/.next +**/.next/cache \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..4f7f87385 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,93 @@ +# ---------------------- # +# STEP 1: Base 설치 환경 # +# ---------------------- # +FROM node:20-alpine AS base + +WORKDIR /repo + +# pnpm 설치 (corepack) +RUN corepack enable && corepack prepare pnpm@10.12.1 --activate + +# 필요한 시스템 패키지 설치 (선택) +RUN apk add --no-cache libc6-compat + +# ---------------------- # +# STEP 2: 의존성 설치용 캐시 레이어 # +# ---------------------- # +FROM base AS deps + +# workspace 정의 복사 +COPY pnpm-workspace.yaml ./ +COPY package.json pnpm-lock.yaml ./ +COPY apps/cs/package.json ./apps/cs/ +COPY packages ./packages + +# 종속성 설치 (only production dependencies) +RUN rm -rf node_modules .next +RUN pnpm install --frozen-lockfile --node-linker=node-modules +#RUN pnpm install --frozen-lockfile --node-linker=node-modules +RUN ls -al . +#RUN pnpm install --prod + +# ---------------------- # +# STEP 3: 빌드 단계 # +# ---------------------- # +FROM base AS builder + +COPY --from=deps /repo /repo +WORKDIR /repo + +# 전체 소스 복사 +COPY . . + +RUN ls -al /repo/apps/cs/.next || true +RUN rm -rf /repo/apps/cs/.next +RUN ls -al /repo/apps/cs/ +# Next.js 빌드 +RUN cat /repo/apps/cs/next.config.ts || true +RUN pnpm --filter ./apps/cs... build + +#RUN ls -al .next/standalone +# 👉 실행에 필요한 모듈만 유지 +#RUN pnpm prune --prod + +# Next.js 빌드 내용 보기 +RUN ls -al ./apps/cs/ +RUN ls -al /repo/apps/cs/.next +RUN ls -al /repo/apps/cs/.next/chunks || true + +# web browser request zip format +#RUN find /repo/apps/cs/.next/static -type f -name '*.js' -exec gzip -k {} \; + +#RUN find ./apps/cs/.next -type f + +# ---------------------- # +# STEP 4: 최종 러너 단계 (최소 런타임) # +# ---------------------- # +FROM node:20-alpine AS runner + +WORKDIR /app + +# corepack 사용 설정 +#RUN corepack enable && corepack prepare pnpm@8.15.5 --activate + +# 환경변수 설정 +ENV NODE_ENV=production +ENV PORT=3000 + +# pnpm은 런타임에 필요 없음 → corepack 설치 생략 가능 + +# 빌드 결과만 복사 +RUN rm -rf .next +COPY --from=builder /repo/apps/cs/.next/standalone ./ +COPY --from=builder /repo/apps/cs/public ./public +COPY --from=builder /repo/apps/cs/.next/static ./apps/cs/.next/static +COPY --from=builder /repo/apps/cs/.env ./apps/cs/.env +COPY --from=builder /repo/apps/cs/package.json ./apps/cs/package.json +COPY --from=builder /repo/apps/cs/next.config.ts ./apps/cs/next.config.ts +# 헬스체크 등 필요 시 추가 가능 +#HEALTHCHECK --interval=30s --timeout=3s CMD wget --spider -q http://localhost:3000 || exit 1 + +EXPOSE 3000 +CMD ["node", "apps/cs/server.js"] +#CMD ["pnpm", "start"] diff --git a/README.md b/README.md index ba078fb13..e90d891aa 100644 --- a/README.md +++ b/README.md @@ -110,15 +110,15 @@ const nextConfig: NextConfig = { { type: "header", key: "referer", - value: "(.*)/pst(.*)", + value: "(.*)/cms(.*)", }, ], }, ], afterFiles: [ { - source: "/pst/:path*", - destination: `${subDomain}/pst/:path*`, + source: "/cms/:path*", + destination: `${subDomain}/cms/:path*`, }, ], fallback: [], @@ -127,6 +127,7 @@ const nextConfig: NextConfig = { }; export default nextConfig; + ``` ### 6. globals.css(또는 전역 css) 수정 @@ -150,6 +151,26 @@ rm -rf node_modules pnpm-lock.yaml pnpm install ``` +### 8. 로그아웃 처리 가이드 + + * 참고 파일 + - apps/cs/app/api/auth/logout/route.ts + - packages/common/components/src/ui/header/index.tsx (55-65 line) + +각 서비스 폴더에 ‘/app/api/auth/logout/route.ts’ 생성해야 합니다(코드는 cs에서 확인). + +** 로그아웃 처리되면 ‘/auth/login’ 페이지로 이동됩니다. + +### 9. 'SITE_ID’ 환경변수를 쿠키로 대체하는 가이드(공통 모듈 사용하기위한 필수 사항) + + * 참고 파일: apps/cs/src/shared/api/client.ts (쿠키 저장 처리) + apps/cs/src/shared/lib/cookie.ts (쿠키 공통 모듈) + + `SITE_ID` 환경변수를 처음 사용하는 파일 또는 최상단 클라이언트 컴포넌트에서 “site-id-key” 쿠키에 `SITE_ID` 저장 + + + + ## 기술 스택 - React(^18) diff --git a/apps/common/features/app/(with-base-layout)/(contents)/cntns/[contsSn]/page.tsx b/apps/common/features/app/(with-base-layout)/(contents)/cntns/[contsSn]/page.tsx new file mode 100644 index 000000000..e8f07bc6b --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/(contents)/cntns/[contsSn]/page.tsx @@ -0,0 +1,10 @@ +import { ContentsPage as VContentsPage } from "@/pages/contents"; + +export default async function ContentsPage({ + params, +}: { + params: Promise<{ contsSn: string }>; +}) { + const { contsSn } = await params; + return ; +} diff --git a/apps/common/features/app/(with-base-layout)/(contents)/layout.tsx b/apps/common/features/app/(with-base-layout)/(contents)/layout.tsx new file mode 100644 index 000000000..934be4d6b --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/(contents)/layout.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { MenuApi } from "@/entities/menu"; +import { useMenuStore } from "@/shared/lib"; +import { ENDPOINT } from "@/shared/config"; +import { PageLayout } from "@/shared/ui/page-layout"; + +export default function ContentsLayout({ + children, +}: { + children: React.ReactNode; +}) { + const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo); + const { data } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.MENUS, currentMenuInfo?.menuCd], + queryFn: () => + MenuApi.getMenuInfo(currentMenuInfo?.menuCd.toString() ?? ""), + enabled: !!currentMenuInfo, + }); + const menuPaths = data?.menuPath?.split(">").map((path) => path.trim()); + + return ( + + {children} + + ); +} diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsfaq/[bbsId]/[pstNo]/page.tsx similarity index 82% rename from apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/[pstNo]/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsfaq/[bbsId]/[pstNo]/page.tsx index 9d3f6c0e3..7c8d6ee86 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/[pstNo]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsfaq/[bbsId]/[pstNo]/page.tsx @@ -1,4 +1,4 @@ -import { ModifyPage } from "@/pages/(pst)/faq"; +import { ModifyPage } from "@/pages/(post)/faq"; export default async function PstBbsFaqPostModifyPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsfaq/[bbsId]/page.tsx similarity index 79% rename from apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsfaq/[bbsId]/page.tsx index 34dd522bf..12fe535a5 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsfaq/[bbsId]/page.tsx @@ -1,4 +1,4 @@ -import { FaqPage } from "@/pages/(pst)/faq"; +import { FaqPage } from "@/pages/(post)/faq"; export default async function PstBbsFaqPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsfaq/[bbsId]/register/page.tsx similarity index 79% rename from apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/register/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsfaq/[bbsId]/register/page.tsx index 6a044f876..e768a48db 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/register/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsfaq/[bbsId]/register/page.tsx @@ -1,4 +1,4 @@ -import { CreatePage } from "@/pages/(pst)/faq"; +import { CreatePage } from "@/pages/(post)/faq"; export default async function PstBbsFaqRegisterPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/[pstNo]/modify/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/[pstNo]/modify/page.tsx similarity index 81% rename from apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/[pstNo]/modify/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/[pstNo]/modify/page.tsx index d7601ffd1..0ae940a3e 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/[pstNo]/modify/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/[pstNo]/modify/page.tsx @@ -1,4 +1,4 @@ -import { ModifyPage } from "@/pages/(pst)/image"; +import { ModifyPage } from "@/pages/(post)/image"; export default async function PstBbsImagePostModifyPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/[pstNo]/page.tsx similarity index 81% rename from apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/[pstNo]/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/[pstNo]/page.tsx index 9ca687f78..7b70daf45 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/[pstNo]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/[pstNo]/page.tsx @@ -1,4 +1,4 @@ -import { ImagePost } from "@/pages/(pst)/image"; +import { ImagePost } from "@/pages/(post)/image"; export default async function PstBbsImagePostPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/page.tsx similarity index 78% rename from apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/page.tsx index 520cfc83a..add5e7dde 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/page.tsx @@ -1,4 +1,4 @@ -import { ImagePage } from "@/pages/(pst)/image"; +import { ImagePage } from "@/pages/(post)/image"; export default async function PstBbsImagePage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/register/page.tsx similarity index 78% rename from apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/register/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/register/page.tsx index c5e3e68c0..537b7f2af 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/register/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsimage/[bbsId]/register/page.tsx @@ -1,4 +1,4 @@ -import { CreatePage } from "@/pages/(pst)/image"; +import { CreatePage } from "@/pages/(post)/image"; export default async function PstBbsImageRegisterPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/[pstNo]/modify/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/[pstNo]/modify/page.tsx similarity index 81% rename from apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/[pstNo]/modify/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/[pstNo]/modify/page.tsx index 76f2d06c3..7cd9664a6 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/[pstNo]/modify/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/[pstNo]/modify/page.tsx @@ -1,4 +1,4 @@ -import { ModifyPage } from "@/pages/(pst)/movie"; +import { ModifyPage } from "@/pages/(post)/movie"; export default async function PstBbsMoviePostModifyPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/[pstNo]/page.tsx similarity index 81% rename from apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/[pstNo]/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/[pstNo]/page.tsx index e0983bd61..51615fd10 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/[pstNo]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/[pstNo]/page.tsx @@ -1,4 +1,4 @@ -import { MoviePost } from "@/pages/(pst)/movie"; +import { MoviePost } from "@/pages/(post)/movie"; export default async function PstBbsMoviePostPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/page.tsx similarity index 78% rename from apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/page.tsx index bbd6b2619..b5d9daf91 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/page.tsx @@ -1,4 +1,4 @@ -import { MoviePage } from "@/pages/(pst)/movie"; +import { MoviePage } from "@/pages/(post)/movie"; export default async function PstBbsMoviePage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/register/page.tsx similarity index 78% rename from apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/register/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/register/page.tsx index 5cfffb031..368832658 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/register/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsmovie/[bbsId]/register/page.tsx @@ -1,4 +1,4 @@ -import { CreatePage } from "@/pages/(pst)/movie"; +import { CreatePage } from "@/pages/(post)/movie"; export default async function PstBbsMovieRegisterPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/answer/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/[pstNo]/answer/page.tsx similarity index 81% rename from apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/answer/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/[pstNo]/answer/page.tsx index bf13cb3e7..ad83352bc 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/answer/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/[pstNo]/answer/page.tsx @@ -1,4 +1,4 @@ -import { CreatePage } from "@/pages/(pst)/normal"; +import { CreatePage } from "@/pages/(post)/normal"; export default async function PstBbsNormalAnswerPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx similarity index 81% rename from apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx index d5ab3ee62..25b98f655 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx @@ -1,4 +1,4 @@ -import { ModifyPage } from "@/pages/(pst)/normal"; +import { ModifyPage } from "@/pages/(post)/normal"; export default async function PstBbsNormalPostModifyPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/[pstNo]/page.tsx similarity index 81% rename from apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/[pstNo]/page.tsx index aa2120e7c..17d7c35cb 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/[pstNo]/page.tsx @@ -1,4 +1,4 @@ -import { NormalPost } from "@/pages/(pst)/normal"; +import { NormalPost } from "@/pages/(post)/normal"; export default async function PstBbsNormalPostPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/page.tsx similarity index 77% rename from apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/page.tsx index eecbc7c33..b1e4a9318 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/page.tsx @@ -1,4 +1,4 @@ -import { NormalPage } from "@/pages/(pst)/normal"; +import { NormalPage } from "@/pages/(post)/normal"; export default async function PstBbsNormalPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/register/page.tsx similarity index 78% rename from apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/register/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/register/page.tsx index 122c87dc4..9f7fe74cc 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/register/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsnormal/[bbsId]/register/page.tsx @@ -1,4 +1,4 @@ -import { CreatePage } from "@/pages/(pst)/normal"; +import { CreatePage } from "@/pages/(post)/normal"; export default async function PstBbsNormalRegisterPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/[pstNo]/modify/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/[pstNo]/modify/page.tsx similarity index 81% rename from apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/[pstNo]/modify/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/[pstNo]/modify/page.tsx index bfe86c491..8ee233879 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/[pstNo]/modify/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/[pstNo]/modify/page.tsx @@ -1,4 +1,4 @@ -import { ModifyPage } from "@/pages/(pst)/notice"; +import { ModifyPage } from "@/pages/(post)/notice"; export default async function PstBbsNoticePostModifyPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/[pstNo]/page.tsx similarity index 81% rename from apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/[pstNo]/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/[pstNo]/page.tsx index f5b6f13fd..5e080048b 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/[pstNo]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/[pstNo]/page.tsx @@ -1,4 +1,4 @@ -import { NoticePost } from "@/pages/(pst)/notice"; +import { NoticePost } from "@/pages/(post)/notice"; export default async function PstBbsNoticePostPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/page.tsx similarity index 77% rename from apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/page.tsx index a94302706..7694f9005 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/page.tsx @@ -1,4 +1,4 @@ -import { NoticePage } from "@/pages/(pst)/notice"; +import { NoticePage } from "@/pages/(post)/notice"; export default async function PstBbsNoticePage({ params, diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/register/page.tsx similarity index 78% rename from apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/register/page.tsx rename to apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/register/page.tsx index 859274306..6ea34f58f 100644 --- a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/register/page.tsx +++ b/apps/common/features/app/(with-base-layout)/(post)/bbsnotice/[bbsId]/register/page.tsx @@ -1,4 +1,4 @@ -import { CreatePage } from "@/pages/(pst)/notice"; +import { CreatePage } from "@/pages/(post)/notice"; export default async function PstBbsNoticeRegisterPage({ params, diff --git a/apps/common/features/app/(with-base-layout)/(post)/layout.tsx b/apps/common/features/app/(with-base-layout)/(post)/layout.tsx new file mode 100644 index 000000000..7f4b1e26b --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/(post)/layout.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { MenuApi } from "@/entities/menu"; +import { ENDPOINT } from "@/shared/config"; +import { useMenuStore } from "@/shared/lib"; +import { PageLayout } from "@/shared/ui/page-layout"; +import { useQuery } from "@tanstack/react-query"; + +export default function PostLayout({ + children, +}: { + children: React.ReactNode; +}) { + const { bbsInfo, currentMenuInfo } = useMenuStore((state) => state); + const { data } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.MENUS, currentMenuInfo?.menuCd], + queryFn: () => + MenuApi.getMenuInfo(currentMenuInfo?.menuCd.toString() ?? ""), + enabled: !!currentMenuInfo, + }); + const menuPaths = data?.menuPath?.split(">").map((path) => path.trim()); + + return ( + + {children} + + ); +} diff --git a/apps/common/features/app/(with-base-layout)/layout.tsx b/apps/common/features/app/(with-base-layout)/layout.tsx index 970ccce5e..5bbd7d024 100644 --- a/apps/common/features/app/(with-base-layout)/layout.tsx +++ b/apps/common/features/app/(with-base-layout)/layout.tsx @@ -3,7 +3,7 @@ import { usePathname, useRouter } from "next/navigation"; import { useQueryClient } from "@tanstack/react-query"; import { BaseLayout } from "@common/components"; -import type { MenuItem } from "@/entities/menu"; +import type { MenuRole } from "@/entities/menu"; import { useMenuStore } from "@/shared/lib"; export default function WithBaseLayout({ @@ -17,7 +17,7 @@ export default function WithBaseLayout({ const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo); - const handleMenuClick = (item: MenuItem) => { + const handleMenuClick = (item: MenuRole) => { setCurrentMenuInfo(item); router.push(item.urlPath); }; diff --git a/apps/common/features/app/(with-base-layout)/pst/layout.tsx b/apps/common/features/app/(with-base-layout)/pst/layout.tsx deleted file mode 100644 index 3f700d076..000000000 --- a/apps/common/features/app/(with-base-layout)/pst/layout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -import { useMenuStore } from "@/shared/lib"; -import { PageLayout } from "@/shared/ui/page-layout"; - -export default function PostLayout({ - children, -}: { - children: React.ReactNode; -}) { - const bbsInfo = useMenuStore((state) => state.bbsInfo); - - return ( - - {children} - - ); -} diff --git a/apps/common/features/app/api/auth/logout/route.ts b/apps/common/features/app/api/auth/logout/route.ts new file mode 100644 index 000000000..fdcc9df7c --- /dev/null +++ b/apps/common/features/app/api/auth/logout/route.ts @@ -0,0 +1,17 @@ +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; +import { + ACCESS_TOKEN, + AUTH_USER_ID, + CLAIM_NAME, + REFRESH_TOKEN, +} from "@/shared/config"; + +export async function POST() { + const cookieStore = await cookies(); + cookieStore.delete(ACCESS_TOKEN); + cookieStore.delete(REFRESH_TOKEN); + cookieStore.delete(AUTH_USER_ID); + cookieStore.delete(CLAIM_NAME); + return NextResponse.json({ message: "Logged out" }, { status: 200 }); +} diff --git a/apps/common/features/app/api/proxy/[...url]/route.ts b/apps/common/features/app/api/proxy/[...url]/route.ts index d3092c5c1..5d81c93fb 100644 --- a/apps/common/features/app/api/proxy/[...url]/route.ts +++ b/apps/common/features/app/api/proxy/[...url]/route.ts @@ -4,7 +4,6 @@ import { CLAIM_NAME, REFRESH_TOKEN, SERVER_API_URL, - SITE_ID, } from "@/shared/config"; import { cookies } from "next/headers"; import { NextResponse } from "next/server"; @@ -15,9 +14,10 @@ async function handler( method: "GET" | "POST" | "PUT" | "DELETE", ) { const cookieStore = await cookies(); + const siteId = cookieStore.get("site-id-key")?.value ?? ""; const headers: Record = { "Content-Type": "application/json", - "X-Site-Id": SITE_ID, + "X-Site-Id": siteId, }; let body = {}; const isRefreshHandler = params.url.includes("refresh"); diff --git a/apps/common/features/next.config.ts b/apps/common/features/next.config.ts index cb651cdc0..34965cf32 100644 --- a/apps/common/features/next.config.ts +++ b/apps/common/features/next.config.ts @@ -1,5 +1,16 @@ import type { NextConfig } from "next"; -const nextConfig: NextConfig = {}; +const nextConfig: NextConfig = { + async rewrites() { + return { + afterFiles: [ + { + source: "/cms/:path*", + destination: "/:path*", + }, + ], + }; + }, +}; export default nextConfig; diff --git a/apps/common/features/package.json b/apps/common/features/package.json index 0bd642b8e..64051045c 100644 --- a/apps/common/features/package.json +++ b/apps/common/features/package.json @@ -29,9 +29,11 @@ "dayjs": "^1.11.13", "js-cookie": "^3.0.5", "next": "15.3.0", + "next-themes": "^0.4.6", "react-chartjs-2": "^5.3.0", "react-dnd": "^16.0.1", "react-hook-form": "^7.54.2", + "sonner": "^2.0.1", "tailwindcss-animate": "^1.0.7", "tus-js-client": "^4.3.1", "zod": "^3.24.2", @@ -39,7 +41,6 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", - "@faker-js/faker": "^9.5.0", "@types/js-cookie": "^3.0.6", "@types/node": "^22.13.1", "@types/webpack": "^5.28.5", diff --git a/apps/common/features/src/app/providers/app-provider.tsx b/apps/common/features/src/app/providers/app-provider.tsx index 721f04bb6..97713c41e 100644 --- a/apps/common/features/src/app/providers/app-provider.tsx +++ b/apps/common/features/src/app/providers/app-provider.tsx @@ -5,10 +5,11 @@ import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { ConfirmProvider } from "@frontend-opensource/use-react-hooks"; import { AuthWrapper } from "@/entities/auth"; import { MenuWrapper } from "@/entities/menu"; -import { StoreProvider, PromptProvider } from "@/shared/providers"; +import { Toaster } from "@/shared/ui/sonner"; import { AlertDialog } from "@/shared/ui/alert-dialog"; import { PromptDialog } from "@/shared/ui/prompt-dialog"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; +import { StoreProvider, PromptProvider } from "@/shared/providers"; const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { const queryClient = new QueryClient(); @@ -22,6 +23,7 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { + diff --git a/apps/common/features/src/entities/content/api/contents-service.ts b/apps/common/features/src/entities/content/api/contents-service.ts index 00d2a8c67..7a55ad03d 100644 --- a/apps/common/features/src/entities/content/api/contents-service.ts +++ b/apps/common/features/src/entities/content/api/contents-service.ts @@ -19,6 +19,17 @@ export class ContentsApiService implements ContentsService { return response.data; } + public async getLatestApprovedContentsInfo( + contsSn: number, + menuCd: string, + ): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.CONTENTS}/${contsSn}`, + { params: { menuCd } }, + ); + return response.data; + } + public async getContentsVersionList( params: ContentsVersionParams, ): Promise { diff --git a/apps/common/features/src/entities/content/model/contents-interface.ts b/apps/common/features/src/entities/content/model/contents-interface.ts index 72456149b..d30840164 100644 --- a/apps/common/features/src/entities/content/model/contents-interface.ts +++ b/apps/common/features/src/entities/content/model/contents-interface.ts @@ -65,6 +65,7 @@ export const contentsDetailSchema = z.object({ aprvDt: z.string(), autzr: z.string(), useYn: z.string(), + menuPicUserUnqIdNm: z.string().nullish(), }); export const contentsFormSchema = contentsDetailSchema.omit({ @@ -81,6 +82,10 @@ export type ContentsVersionList = PageableList; export interface ContentsService { getContentsList(params: ContentsParams): Promise; + getLatestApprovedContentsInfo( + contsSn: number, + menuCd: string, + ): Promise; getContentsVersionList( params: ContentsVersionParams, ): Promise; diff --git a/apps/common/features/src/entities/menu-authority/index.ts b/apps/common/features/src/entities/menu-authority/index.ts index baf6e6085..da36accae 100644 --- a/apps/common/features/src/entities/menu-authority/index.ts +++ b/apps/common/features/src/entities/menu-authority/index.ts @@ -1,3 +1,4 @@ export { MenuAuthorityApi } from "./api/menu-authority-service"; export { type PageMenuAuthority } from "./model/menu-authority-interface"; +export { AuthorityButton } from "./ui/authority-button"; export { ManagementAuthorityDialog } from "./ui/management-authority-dialog"; diff --git a/apps/common/features/src/entities/menu-authority/ui/authority-button.tsx b/apps/common/features/src/entities/menu-authority/ui/authority-button.tsx new file mode 100644 index 000000000..257ab6e5d --- /dev/null +++ b/apps/common/features/src/entities/menu-authority/ui/authority-button.tsx @@ -0,0 +1,26 @@ +import { useMenuStore } from "@/shared/lib"; +import { Button, cn, type ButtonProps } from "@common/components"; +import type { PageMenuAuthority } from "../model/menu-authority-interface"; + +type AuthorityButtonProps = { + authority: keyof PageMenuAuthority; +} & ButtonProps; + +function AuthorityButton({ + authority, + className, + children, + ...props +}: AuthorityButtonProps) { + const pageMenuAuthority = useMenuStore((state) => state.pageMenuAuthority); + + const isAuthority = pageMenuAuthority?.[authority] === "Y"; + + return ( + + ); +} + +export { AuthorityButton }; diff --git a/apps/common/features/src/entities/menu/api/menu-service.ts b/apps/common/features/src/entities/menu/api/menu-service.ts index 4424f8d37..055bc9371 100644 --- a/apps/common/features/src/entities/menu/api/menu-service.ts +++ b/apps/common/features/src/entities/menu/api/menu-service.ts @@ -1,14 +1,21 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; -import type { MenuItem, MenuService } from "../model/menu-interface"; +import type { MenuInfo, MenuRole, MenuService } from "../model/menu-interface"; export class MenuApiService implements MenuService { - async getMenuRoles(siteId: string): Promise { - const response = await client.get( + async getMenuRoles(siteId: string): Promise { + const response = await client.get( `${ENDPOINT.CMS_SERVICE.MENU_ROLES}/${siteId}`, ); return response.data; } + + async getMenuInfo(menuCd: string): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`, + ); + return response.data; + } } export const MenuApi = new MenuApiService(); diff --git a/apps/common/features/src/entities/menu/index.ts b/apps/common/features/src/entities/menu/index.ts index 04f63e38b..458867586 100644 --- a/apps/common/features/src/entities/menu/index.ts +++ b/apps/common/features/src/entities/menu/index.ts @@ -1,2 +1,3 @@ +export { MenuApi } from "./api/menu-service"; export { MenuWrapper } from "./lib/menu-wrapper"; -export type { MenuItem } from "./model/menu-interface"; +export type { MenuRole } from "./model/menu-interface"; diff --git a/apps/common/features/src/entities/menu/lib/menu-wrapper.tsx b/apps/common/features/src/entities/menu/lib/menu-wrapper.tsx index 2855e5ef0..1dafbde35 100644 --- a/apps/common/features/src/entities/menu/lib/menu-wrapper.tsx +++ b/apps/common/features/src/entities/menu/lib/menu-wrapper.tsx @@ -4,10 +4,11 @@ import { useEffect } from "react"; import { usePathname, useRouter } from "next/navigation"; import { MenuAuthorityApi } from "@/entities/menu-authority/@x/menu"; import { getCookie, useAlertStore, useMenuStore } from "@/shared/lib"; -import { SITE_ID } from "@/shared/config"; import { MenuApi } from "../api/menu-service"; import { getCurrentMenu } from "./utils"; +const siteId = getCookie("site-id-key") ?? ""; + const MenuWrapper = ({ children }: { children: React.ReactNode }) => { const router = useRouter(); const pathname = usePathname(); @@ -17,7 +18,7 @@ const MenuWrapper = ({ children }: { children: React.ReactNode }) => { useMenuStore((state) => state); const getCurrentMenuInfo = async () => { - const menuRoles = await MenuApi.getMenuRoles(SITE_ID); + const menuRoles = await MenuApi.getMenuRoles(siteId); if (!menuRoles) return; return getCurrentMenu(menuRoles, pathname ?? ""); }; @@ -44,7 +45,7 @@ const MenuWrapper = ({ children }: { children: React.ReactNode }) => { if (pageMenuAuthority?.length) setPageMenuAuthority(pageMenuAuthority[0]!); })(); - }, []); + }, [pathname]); useEffect(() => { if (!pageMenuAuthority) return; diff --git a/apps/common/features/src/entities/menu/lib/utils.ts b/apps/common/features/src/entities/menu/lib/utils.ts index 1cf4514d9..bb2fadb88 100644 --- a/apps/common/features/src/entities/menu/lib/utils.ts +++ b/apps/common/features/src/entities/menu/lib/utils.ts @@ -1,9 +1,9 @@ -import type { MenuItem } from "../model/menu-interface"; +import type { MenuRole } from "../model/menu-interface"; export const getCurrentMenu = ( - menus: MenuItem[], + menus: MenuRole[], pathname: string, -): MenuItem | null => { +): MenuRole | null => { for (const menu of menus) { if ( menu.urlPath !== "" && diff --git a/apps/common/features/src/entities/menu/model/menu-interface.ts b/apps/common/features/src/entities/menu/model/menu-interface.ts index 12c7d007a..885e02745 100644 --- a/apps/common/features/src/entities/menu/model/menu-interface.ts +++ b/apps/common/features/src/entities/menu/model/menu-interface.ts @@ -1,11 +1,8 @@ -export interface MenuItem { +interface Menu { menuCd: number; menuKornNm: string; menuEngNm: string; - parentId: number | null; sortSeq: number; - iconNm?: string; - children: MenuItem[] | null; levelNo: number; urlPath: string; menuLnkgTypeCd: string; @@ -15,9 +12,30 @@ export interface MenuItem { useYn: string; otptYn: string; newWndwYn: string; +} + +export interface MenuRole extends Menu { + parentId: number | null; + iconNm?: string; + children: MenuRole[] | null; requiredUrlPath: boolean; } -export interface MenuService { - getMenuRoles(siteId: string): Promise; +export interface MenuInfo extends Menu { + siteId: string; + menuSubKornNm: string; + menuSubEngNm: string; + iconName: string; + menuLnkgTypeCdNm: string; + prntMenuCd: number; + evlUseYn: string; + menuLnkgSnNm: string; + menuPicUserUnqId: string; + menuPicUserUnqIdNm: string; + menuPicDsgnYmd: string; +} + +export interface MenuService { + getMenuRoles(siteId: string): Promise; + getMenuInfo(menuCd: string): Promise; } diff --git a/apps/common/features/src/entities/post/api/post-service.ts b/apps/common/features/src/entities/post/api/post-service.ts index 1e5ac5146..c7bd0c3af 100644 --- a/apps/common/features/src/entities/post/api/post-service.ts +++ b/apps/common/features/src/entities/post/api/post-service.ts @@ -31,6 +31,16 @@ export class PostApiService implements PostService { return response.data; } + public async getPostEditInfo( + bbsId: number, + pstNo: number, + ): Promise { + const response = await client.get( + `${ENDPOINT.BOARD_SERVICE.POSTS_USER}/edit/${bbsId}/${pstNo}`, + ); + return response.data; + } + public async createPost(data: PostFormData): Promise { const validatedData = postFormSchema.parse(data); const response = await client.post( diff --git a/apps/common/features/src/entities/post/model/post-interface.ts b/apps/common/features/src/entities/post/model/post-interface.ts index 11778002f..de8c44891 100644 --- a/apps/common/features/src/entities/post/model/post-interface.ts +++ b/apps/common/features/src/entities/post/model/post-interface.ts @@ -9,6 +9,7 @@ export interface PostParams extends PageableParams { prdPstgYn?: string; siteId?: string; bbsTypeCd?: string; + ansUseYn?: string; } export interface Post { @@ -77,6 +78,7 @@ export type PostUpdateFormData = z.infer; export interface PostService { getPostList(bbsId: number, params: PostParams): Promise; getPostInfo(bbsId: number, pstNo: number): Promise; + getPostEditInfo(bbsId: number, pstNo: number): Promise; createPost(data: PostFormData): Promise; updatePost( bbsId: number, diff --git a/apps/common/features/src/pages/(pst)/faq/index.ts b/apps/common/features/src/pages/(post)/faq/index.ts similarity index 100% rename from apps/common/features/src/pages/(pst)/faq/index.ts rename to apps/common/features/src/pages/(post)/faq/index.ts diff --git a/apps/common/features/src/pages/(pst)/faq/ui/create-page.tsx b/apps/common/features/src/pages/(post)/faq/ui/create-page.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/faq/ui/create-page.tsx rename to apps/common/features/src/pages/(post)/faq/ui/create-page.tsx diff --git a/apps/common/features/src/pages/(pst)/faq/ui/faq-page.tsx b/apps/common/features/src/pages/(post)/faq/ui/faq-page.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/faq/ui/faq-page.tsx rename to apps/common/features/src/pages/(post)/faq/ui/faq-page.tsx diff --git a/apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx b/apps/common/features/src/pages/(post)/faq/ui/modify-page.tsx similarity index 95% rename from apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx rename to apps/common/features/src/pages/(post)/faq/ui/modify-page.tsx index 6987cb0bc..5c25d21e0 100644 --- a/apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx +++ b/apps/common/features/src/pages/(post)/faq/ui/modify-page.tsx @@ -20,7 +20,7 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { data: postInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], - queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + queryFn: () => PostApi.getPostEditInfo(parseInt(bbsId), parseInt(pstNo)), }); const { mutateAsync: updatePost } = useMutation({ diff --git a/apps/common/features/src/pages/(pst)/image/index.ts b/apps/common/features/src/pages/(post)/image/index.ts similarity index 100% rename from apps/common/features/src/pages/(pst)/image/index.ts rename to apps/common/features/src/pages/(post)/image/index.ts diff --git a/apps/common/features/src/pages/(pst)/image/ui/create-page.tsx b/apps/common/features/src/pages/(post)/image/ui/create-page.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/image/ui/create-page.tsx rename to apps/common/features/src/pages/(post)/image/ui/create-page.tsx diff --git a/apps/common/features/src/pages/(pst)/image/ui/image-page.tsx b/apps/common/features/src/pages/(post)/image/ui/image-page.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/image/ui/image-page.tsx rename to apps/common/features/src/pages/(post)/image/ui/image-page.tsx diff --git a/apps/common/features/src/pages/(pst)/image/ui/image-post.tsx b/apps/common/features/src/pages/(post)/image/ui/image-post.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/image/ui/image-post.tsx rename to apps/common/features/src/pages/(post)/image/ui/image-post.tsx diff --git a/apps/common/features/src/pages/(pst)/image/ui/modify-page.tsx b/apps/common/features/src/pages/(post)/image/ui/modify-page.tsx similarity index 95% rename from apps/common/features/src/pages/(pst)/image/ui/modify-page.tsx rename to apps/common/features/src/pages/(post)/image/ui/modify-page.tsx index d292fea42..a68d24a86 100644 --- a/apps/common/features/src/pages/(pst)/image/ui/modify-page.tsx +++ b/apps/common/features/src/pages/(post)/image/ui/modify-page.tsx @@ -20,7 +20,7 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { data: postInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], - queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + queryFn: () => PostApi.getPostEditInfo(parseInt(bbsId), parseInt(pstNo)), }); const { mutateAsync: updatePost } = useMutation({ diff --git a/apps/common/features/src/pages/(pst)/movie/index.ts b/apps/common/features/src/pages/(post)/movie/index.ts similarity index 100% rename from apps/common/features/src/pages/(pst)/movie/index.ts rename to apps/common/features/src/pages/(post)/movie/index.ts diff --git a/apps/common/features/src/pages/(pst)/movie/ui/create-page.tsx b/apps/common/features/src/pages/(post)/movie/ui/create-page.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/movie/ui/create-page.tsx rename to apps/common/features/src/pages/(post)/movie/ui/create-page.tsx diff --git a/apps/common/features/src/pages/(pst)/movie/ui/modify-page.tsx b/apps/common/features/src/pages/(post)/movie/ui/modify-page.tsx similarity index 95% rename from apps/common/features/src/pages/(pst)/movie/ui/modify-page.tsx rename to apps/common/features/src/pages/(post)/movie/ui/modify-page.tsx index f9fc11f3d..815112523 100644 --- a/apps/common/features/src/pages/(pst)/movie/ui/modify-page.tsx +++ b/apps/common/features/src/pages/(post)/movie/ui/modify-page.tsx @@ -20,7 +20,7 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { data: postInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], - queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + queryFn: () => PostApi.getPostEditInfo(parseInt(bbsId), parseInt(pstNo)), }); const { mutateAsync: updatePost } = useMutation({ diff --git a/apps/common/features/src/pages/(pst)/movie/ui/movie-page.tsx b/apps/common/features/src/pages/(post)/movie/ui/movie-page.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/movie/ui/movie-page.tsx rename to apps/common/features/src/pages/(post)/movie/ui/movie-page.tsx diff --git a/apps/common/features/src/pages/(pst)/movie/ui/movie-post.tsx b/apps/common/features/src/pages/(post)/movie/ui/movie-post.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/movie/ui/movie-post.tsx rename to apps/common/features/src/pages/(post)/movie/ui/movie-post.tsx diff --git a/apps/common/features/src/pages/(pst)/normal/index.ts b/apps/common/features/src/pages/(post)/normal/index.ts similarity index 100% rename from apps/common/features/src/pages/(pst)/normal/index.ts rename to apps/common/features/src/pages/(post)/normal/index.ts diff --git a/apps/common/features/src/pages/(pst)/normal/ui/create-page.tsx b/apps/common/features/src/pages/(post)/normal/ui/create-page.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/normal/ui/create-page.tsx rename to apps/common/features/src/pages/(post)/normal/ui/create-page.tsx diff --git a/apps/common/features/src/pages/(pst)/normal/ui/modify-page.tsx b/apps/common/features/src/pages/(post)/normal/ui/modify-page.tsx similarity index 95% rename from apps/common/features/src/pages/(pst)/normal/ui/modify-page.tsx rename to apps/common/features/src/pages/(post)/normal/ui/modify-page.tsx index c47b1999c..9c2ef6f5f 100644 --- a/apps/common/features/src/pages/(pst)/normal/ui/modify-page.tsx +++ b/apps/common/features/src/pages/(post)/normal/ui/modify-page.tsx @@ -20,7 +20,7 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { data: postInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], - queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + queryFn: () => PostApi.getPostEditInfo(parseInt(bbsId), parseInt(pstNo)), }); const { mutateAsync: updatePost } = useMutation({ diff --git a/apps/common/features/src/pages/(pst)/normal/ui/normal-page.tsx b/apps/common/features/src/pages/(post)/normal/ui/normal-page.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/normal/ui/normal-page.tsx rename to apps/common/features/src/pages/(post)/normal/ui/normal-page.tsx diff --git a/apps/common/features/src/pages/(pst)/normal/ui/normal-post.tsx b/apps/common/features/src/pages/(post)/normal/ui/normal-post.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/normal/ui/normal-post.tsx rename to apps/common/features/src/pages/(post)/normal/ui/normal-post.tsx diff --git a/apps/common/features/src/pages/(pst)/notice/index.ts b/apps/common/features/src/pages/(post)/notice/index.ts similarity index 100% rename from apps/common/features/src/pages/(pst)/notice/index.ts rename to apps/common/features/src/pages/(post)/notice/index.ts diff --git a/apps/common/features/src/pages/(pst)/notice/model/notice-table-columns.tsx b/apps/common/features/src/pages/(post)/notice/model/notice-table-columns.tsx similarity index 89% rename from apps/common/features/src/pages/(pst)/notice/model/notice-table-columns.tsx rename to apps/common/features/src/pages/(post)/notice/model/notice-table-columns.tsx index 0926a2d9d..e6feb6952 100644 --- a/apps/common/features/src/pages/(pst)/notice/model/notice-table-columns.tsx +++ b/apps/common/features/src/pages/(post)/notice/model/notice-table-columns.tsx @@ -8,6 +8,12 @@ export const columns: ColumnDef[] = [ { accessorKey: "rowNo", header: "NO", + cell: ({ row, table }) => { + const totalRows = table.getRowCount(); + const currentPage = table.getState().pagination.pageIndex; + const pageSize = table.getState().pagination.pageSize; + return totalRows - (currentPage * pageSize + row.index); + }, }, { accessorKey: "pstTtl", diff --git a/apps/common/features/src/pages/(pst)/notice/ui/create-page.tsx b/apps/common/features/src/pages/(post)/notice/ui/create-page.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/notice/ui/create-page.tsx rename to apps/common/features/src/pages/(post)/notice/ui/create-page.tsx diff --git a/apps/common/features/src/pages/(pst)/notice/ui/modify-page.tsx b/apps/common/features/src/pages/(post)/notice/ui/modify-page.tsx similarity index 95% rename from apps/common/features/src/pages/(pst)/notice/ui/modify-page.tsx rename to apps/common/features/src/pages/(post)/notice/ui/modify-page.tsx index 7a46a7d6b..0e04d616a 100644 --- a/apps/common/features/src/pages/(pst)/notice/ui/modify-page.tsx +++ b/apps/common/features/src/pages/(post)/notice/ui/modify-page.tsx @@ -20,7 +20,7 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { data: postInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], - queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + queryFn: () => PostApi.getPostEditInfo(parseInt(bbsId), parseInt(pstNo)), }); const { mutateAsync: updatePost } = useMutation({ diff --git a/apps/common/features/src/pages/(pst)/notice/ui/notice-page.tsx b/apps/common/features/src/pages/(post)/notice/ui/notice-page.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/notice/ui/notice-page.tsx rename to apps/common/features/src/pages/(post)/notice/ui/notice-page.tsx diff --git a/apps/common/features/src/pages/(pst)/notice/ui/notice-post.tsx b/apps/common/features/src/pages/(post)/notice/ui/notice-post.tsx similarity index 100% rename from apps/common/features/src/pages/(pst)/notice/ui/notice-post.tsx rename to apps/common/features/src/pages/(post)/notice/ui/notice-post.tsx diff --git a/apps/common/features/src/pages/contents/api/menu-evaluation-service.ts b/apps/common/features/src/pages/contents/api/menu-evaluation-service.ts new file mode 100644 index 000000000..963b7ad19 --- /dev/null +++ b/apps/common/features/src/pages/contents/api/menu-evaluation-service.ts @@ -0,0 +1,59 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + MenuEvaluation, + MenuEvaluationDetail, + MenuEvaluationParams, + MenuEvaluationService, + MenuEvaluationCreateRequestDto, + MenuEvaluationUpdateRequestDto, + PageMenuEvaluationListResponseDto, +} from "../model/menu-evaluation-interface"; + +export class MenuEvaluationApiService implements MenuEvaluationService { + public async getMenuEvaluationList( + params: MenuEvaluationParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.MENU_EVALUATION}`, + { params }, + ); + return response.data; + } + + public async getMenuEvaluation( + menuEvlSn: number, + ): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.MENU_EVALUATION}/${menuEvlSn}`, + ); + return response.data; + } + + public async createMenuEvaluation( + data: MenuEvaluationCreateRequestDto, + ): Promise { + const response = await client.post( + `${ENDPOINT.CMS_SERVICE.MENU_EVALUATION}`, + data, + ); + return response.data; + } + + public async updateMenuEvaluation( + menuEvlSn: number, + data: MenuEvaluationUpdateRequestDto, + ): Promise { + const response = await client.put( + `${ENDPOINT.CMS_SERVICE.MENU_EVALUATION}/${menuEvlSn}`, + data, + ); + return response.data; + } + + public async deleteMenuEvaluation(menuEvlSn: number): Promise { + await client.delete(`${ENDPOINT.CMS_SERVICE.MENU_EVALUATION}/${menuEvlSn}`); + } +} + +export const MenuEvaluationApi = new MenuEvaluationApiService(); diff --git a/apps/common/features/src/pages/contents/api/menu-modification-demand-service.ts b/apps/common/features/src/pages/contents/api/menu-modification-demand-service.ts new file mode 100644 index 000000000..9961e0946 --- /dev/null +++ b/apps/common/features/src/pages/contents/api/menu-modification-demand-service.ts @@ -0,0 +1,66 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + MenuModificationDemand, + MenuModificationDemandCreateRequestDto, + MenuModificationDemandDetail, + MenuModificationDemandParams, + MenuModificationDemandService, + MenuModificationDemandUpdateRequestDto, + PageMenuModificationDemandListResponseDto, +} from "../model/menu-modification-demand-interface"; + +export class MenuModificationDemandApiService + implements MenuModificationDemandService +{ + public async getMenuModificationDemandList( + params: MenuModificationDemandParams, + ): Promise { + const response = + await client.get( + `${ENDPOINT.CMS_SERVICE.MENU_MODIFICATION_DEMAND}`, + { params }, + ); + return response.data; + } + + public async getMenuModificationDemand( + mdfcnRcptNo: number, + ): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.MENU_MODIFICATION_DEMAND}/${mdfcnRcptNo}`, + ); + return response.data; + } + + public async createMenuModificationDemand( + data: MenuModificationDemandCreateRequestDto, + ): Promise { + const response = await client.post( + `${ENDPOINT.CMS_SERVICE.MENU_MODIFICATION_DEMAND}`, + data, + ); + return response.data; + } + + public async updateMenuModificationDemand( + mdfcnRcptNo: number, + data: MenuModificationDemandUpdateRequestDto, + ): Promise { + const response = await client.put( + `${ENDPOINT.CMS_SERVICE.MENU_MODIFICATION_DEMAND}/${mdfcnRcptNo}`, + data, + ); + return response.data; + } + + public async deleteMenuModificationDemand( + mdfcnRcptNo: number, + ): Promise { + await client.delete( + `${ENDPOINT.CMS_SERVICE.MENU_MODIFICATION_DEMAND}/${mdfcnRcptNo}`, + ); + } +} + +export const MenuModificationDemandApi = new MenuModificationDemandApiService(); diff --git a/apps/common/features/src/pages/contents/index.ts b/apps/common/features/src/pages/contents/index.ts new file mode 100644 index 000000000..c14f174e2 --- /dev/null +++ b/apps/common/features/src/pages/contents/index.ts @@ -0,0 +1 @@ +export { ContentsPage } from "./ui/contents-page"; diff --git a/apps/common/features/src/pages/contents/model/menu-evaluation-interface.ts b/apps/common/features/src/pages/contents/model/menu-evaluation-interface.ts new file mode 100644 index 000000000..c9166f56c --- /dev/null +++ b/apps/common/features/src/pages/contents/model/menu-evaluation-interface.ts @@ -0,0 +1,49 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface MenuEvaluationParams extends PageableParams { + menuCd?: number; + siteId?: string; + evlIp?: string; +} + +export interface MenuEvaluation { + evlSn: number; + menuCd: number; + siteId: string; + evlDt: string; + evlScr: number; +} + +export interface MenuEvaluationDetail extends MenuEvaluation { + evlIp: string; + userUnqId: string; +} + +export interface MenuEvaluationCreateRequestDto { + menuCd: number; + siteId: string; + evlIp?: string; + evlScr: number; + userUnqId: string; +} + +export interface MenuEvaluationUpdateRequestDto { + evlScr: number; +} + +export type PageMenuEvaluationListResponseDto = PageableList; + +export interface MenuEvaluationService { + getMenuEvaluationList( + params: MenuEvaluationParams, + ): Promise; + getMenuEvaluation(menuEvlSn: number): Promise; + createMenuEvaluation( + data: MenuEvaluationCreateRequestDto, + ): Promise; + updateMenuEvaluation( + menuEvlSn: number, + data: MenuEvaluationUpdateRequestDto, + ): Promise; + deleteMenuEvaluation(menuEvlSn: number): Promise; +} diff --git a/apps/common/features/src/pages/contents/model/menu-modification-demand-interface.ts b/apps/common/features/src/pages/contents/model/menu-modification-demand-interface.ts new file mode 100644 index 000000000..0d9c5b051 --- /dev/null +++ b/apps/common/features/src/pages/contents/model/menu-modification-demand-interface.ts @@ -0,0 +1,68 @@ +import type { PageableList, PageableParams } from "@/shared/model"; +import { z } from "zod"; + +export interface MenuModificationDemandParams extends PageableParams { + menuCd?: number; + siteId?: string; + mdfcnDmndSttusCd?: string; +} + +export interface MenuModificationDemand { + mdfcnRcptNo: number; + menuCd: number; + rcptDt: string; + aplcnt: string; + aplcnYn: string; +} + +export interface MenuModificationDemandDetail extends MenuModificationDemand { + mdfcnCn: string; + userUnqId: string; + aplcntEml: string; +} + +export const menuModificationDemandCreateRequestDtoSchema = z.object({ + menuCd: z.number(), + mdfcnCn: z.string(), + userUnqId: z.string().optional(), + aplcnt: z.string(), + aplcntEml: z.string().optional(), +}); + +export const menuModificationDemandUpdateRequestDtoSchema = z.object({ + mdfcnCn: z.string(), + aplcntEml: z.string().optional(), + aplcnYn: z.string(), +}); + +export type MenuModificationDemandCreateRequestDto = z.infer< + typeof menuModificationDemandCreateRequestDtoSchema +>; + +export type MenuModificationDemandUpdateRequestDto = z.infer< + typeof menuModificationDemandUpdateRequestDtoSchema +>; + +export type PageMenuModificationDemandListResponseDto = + PageableList; + +export interface MenuModificationDemandService { + getMenuModificationDemandList( + params: MenuModificationDemandParams, + ): Promise; + + getMenuModificationDemand( + mdfcnRcptNo: number, + ): Promise; + + createMenuModificationDemand( + data: MenuModificationDemandCreateRequestDto, + ): Promise; + + updateMenuModificationDemand( + mdfcnRcptNo: number, + data: MenuModificationDemandUpdateRequestDto, + ): Promise; + + deleteMenuModificationDemand(mdfcnRcptNo: number): Promise; +} diff --git a/apps/common/features/src/pages/contents/ui/contents-page.tsx b/apps/common/features/src/pages/contents/ui/contents-page.tsx new file mode 100644 index 000000000..46ab9669e --- /dev/null +++ b/apps/common/features/src/pages/contents/ui/contents-page.tsx @@ -0,0 +1,163 @@ +"use client"; + +import dayjs from "dayjs"; +import Image from "next/image"; +import { toast } from "sonner"; +import { useEffect, useRef, useState } from "react"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { useClipboard } from "@frontend-opensource/use-react-hooks"; +import { + Button, + getCookie, + Label, + RadioGroup, + RadioGroupItem, +} from "@common/components"; +import { CodeApi } from "@/entities/code"; +import { ContentsApi } from "@/entities/content"; +import { AUTH_USER_ID, ENDPOINT } from "@/shared/config"; +import { useAlertStore, useMenuStore } from "@/shared/lib"; +import { MenuEvaluationApi } from "../api/menu-evaluation-service"; +import { MenuModificationDemandDialog } from "./menu-modification-demand-dialog"; + +function ContentsPage({ contsSn }: { contsSn: number }) { + const [evlScr, setEvlScr] = useState(5); + const shadowRootRef = useRef(null); + const { copyText, copied } = useClipboard(); + const alert = useAlertStore((state) => state.setMessage); + const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo); + const siteId = getCookie("site-id-key") ?? ""; + const userUnqId = getCookie(AUTH_USER_ID) ?? ""; + + const { data: contentsDetail } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS_DETAILS, contsSn], + queryFn: () => + ContentsApi.getLatestApprovedContentsInfo( + contsSn, + currentMenuInfo?.menuCd.toString() ?? "", + ), + enabled: !!contsSn && !!currentMenuInfo, + }); + + const { data: koglData } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CODE, "KOGLCD"], + queryFn: () => CodeApi.getCode("KOGLCD"), + select: (data) => + data.find((code) => code.lwpoCd === contentsDetail?.koglCd), + enabled: !!contentsDetail, + }); + + const { data: cclData } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CODE, "CCLCD"], + queryFn: () => CodeApi.getCode("CCLCD"), + select: (data) => + data.find((code) => code.lwpoCd === contentsDetail?.cclCd), + enabled: !!contentsDetail, + }); + + const { mutate: menuEvaluation } = useMutation({ + mutationFn: (evlScr: number) => + MenuEvaluationApi.createMenuEvaluation({ + menuCd: currentMenuInfo?.menuCd ?? 0, + siteId, + evlScr, + userUnqId, + }), + onSuccess: () => { + alert("게시물 평가가 완료되었습니다."); + }, + }); + + useEffect(() => { + if (!shadowRootRef.current || !contentsDetail?.contsCn) return; + + const shadowRoot = shadowRootRef.current.attachShadow({ mode: "open" }); + + const styleElement = document.createElement("style"); + styleElement.textContent = `div { font-size: 16px; line-height: 1.5; }`; + shadowRoot.appendChild(styleElement); + + const contentElement = document.createElement("div"); + contentElement.innerHTML = contentsDetail.contsCn; + shadowRoot.appendChild(contentElement); + }, [contentsDetail?.contsCn]); + + useEffect(() => { + if (copied) toast.success("URL이 클립보드에 복사되었습니다."); + }, [copied]); + + return ( + <> +
+
+
+
+
+
+ setEvlScr(parseInt(value))} + className="flex items-center space-x-4" + > +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+
+
+ 최종수정일: {dayjs(contentsDetail?.aprvDt).format("YYYY-MM-DD")} | + 담당자: {contentsDetail?.menuPicUserUnqIdNm} +
+ CCL + CCL + + +
+
+ + ); +} + +export { ContentsPage }; diff --git a/apps/common/features/src/pages/contents/ui/menu-modification-demand-dialog.tsx b/apps/common/features/src/pages/contents/ui/menu-modification-demand-dialog.tsx new file mode 100644 index 000000000..b8fd2b68a --- /dev/null +++ b/apps/common/features/src/pages/contents/ui/menu-modification-demand-dialog.tsx @@ -0,0 +1,173 @@ +import { memo, useEffect, useState } from "react"; +import { useForm } from "react-hook-form"; +import { useMutation } from "@tanstack/react-query"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { + Button, + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Input, + DialogTrigger, + Textarea, +} from "@common/components"; +import { useAlertStore } from "@/shared/lib"; +import { Form, FormField, FormItem, FormControl } from "@/shared/ui/form"; +import { MenuModificationDemandApi } from "../api/menu-modification-demand-service"; +import { + menuModificationDemandCreateRequestDtoSchema, + type MenuModificationDemandCreateRequestDto, +} from "../model/menu-modification-demand-interface"; + +type MenuModificationDemandDialogProps = { + menuCd?: number; +}; + +const MenuModificationDemandDialog = memo( + ({ menuCd }: MenuModificationDemandDialogProps) => { + const [open, setOpen] = useState(false); + const { confirm } = useConfirm(); + const alert = useAlertStore((state) => state.setMessage); + + const { mutateAsync: createMenuModificationDemand } = useMutation({ + mutationFn: (data: MenuModificationDemandCreateRequestDto) => + MenuModificationDemandApi.createMenuModificationDemand(data), + onSuccess: () => { + alert("메뉴 수정 요청이 등록되었습니다.", () => setOpen(false)); + }, + }); + + const form = useForm({ + resolver: zodResolver(menuModificationDemandCreateRequestDtoSchema), + defaultValues: { + menuCd: menuCd ?? 0, + mdfcnCn: "", + aplcnt: "", + aplcntEml: "", + }, + }); + + const onSubmit = async (data: MenuModificationDemandCreateRequestDto) => { + if (await confirm("메뉴 수정 요청을 등록하시겠습니까?")) { + createMenuModificationDemand(data); + } + }; + + useEffect(() => { + if (!open) form.reset(); + }, [open]); + + return ( + + + + + + + 메뉴 수정 요청 + +
+ + + + + + + + + 신청자 + + ( + + + + + + )} + /> + + + + 이메일 + + ( + + + + + + )} + /> + + + + 요청 내용 + + ( + + +