[docker build] 기본 구조 점검
This commit is contained in:
@@ -0,0 +1,232 @@
|
||||
# Next.js App Router의 라우트 그룹(Route Groups) 구현 분석
|
||||
|
||||
## 개요
|
||||
|
||||
Next.js의 App Router에서는 괄호로 묶인 디렉토리(`(with-base-layout)`, `(without-base-layout)`)를 라우트 그룹으로 처리하여 URL 경로에 영향을 주지 않으면서 서로 다른 레이아웃을 적용할 수 있게 합니다. 이 문서에서는 프로젝트에서 라우트 그룹이 어떻게 구현되고 사용되는지 분석합니다.
|
||||
|
||||
## 라우트 그룹 구조 분석
|
||||
|
||||
### 1. 디렉토리 구조
|
||||
|
||||
프로젝트에서는 두 가지 주요 라우트 그룹이 사용되고 있습니다:
|
||||
|
||||
```
|
||||
/apps/cs/app/
|
||||
├── (with-base-layout)/
|
||||
│ ├── banner/
|
||||
│ ├── bbs/
|
||||
│ ├── code/
|
||||
│ ├── contents/
|
||||
│ ├── dashboard/
|
||||
│ ├── ...
|
||||
│ ├── layout.tsx
|
||||
│ └── loading.tsx
|
||||
├── (without-base-layout)/
|
||||
│ ├── user-survey-article/
|
||||
│ └── user-survey-result/
|
||||
└── layout.tsx
|
||||
```
|
||||
|
||||
### 2. 라우트 그룹별 레이아웃 구현
|
||||
|
||||
#### 루트 레이아웃 (`/apps/cs/app/layout.tsx`)
|
||||
|
||||
```tsx
|
||||
import type { Metadata } from "next";
|
||||
import { App } from "@/app";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "유역하수도 통합 정보플랫폼",
|
||||
};
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
#### App 컴포넌트 (`/apps/cs/src/app/index.tsx`)
|
||||
|
||||
```tsx
|
||||
import "./styles/base.css";
|
||||
import "./styles/components.css";
|
||||
import "@fontsource-variable/noto-sans-kr";
|
||||
|
||||
import { AppProvider } from "./providers";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
const App = async ({ children }: Readonly<{ children: React.ReactNode }>) => {
|
||||
const theme = (await cookies()).get("site-theme-key");
|
||||
|
||||
return (
|
||||
<html lang="ko" data-theme={theme?.value}>
|
||||
<body>
|
||||
<AppProvider>{children}</AppProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
};
|
||||
|
||||
export { App };
|
||||
```
|
||||
|
||||
#### (with-base-layout) 레이아웃 (`/apps/cs/app/(with-base-layout)/layout.tsx`)
|
||||
|
||||
```tsx
|
||||
"use client";
|
||||
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { BaseLayout } from "@common/components";
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
import { useMenuStore } from "@/shared/lib";
|
||||
import { PageLayout } from "@/shared/ui/page-layout";
|
||||
|
||||
export default function WithBaseLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo);
|
||||
|
||||
const handleMenuClick = (item: MenuItem) => {
|
||||
setCurrentMenuInfo(item);
|
||||
router.push(item.urlPath);
|
||||
};
|
||||
|
||||
return (
|
||||
<BaseLayout
|
||||
queryClient={queryClient}
|
||||
currentPath={pathname ?? ""}
|
||||
handleMenuClick={handleMenuClick}
|
||||
>
|
||||
<PageLayout>{children}</PageLayout>
|
||||
</BaseLayout>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
#### PageLayout 컴포넌트 (`/apps/cs/src/shared/ui/page-layout.tsx`)
|
||||
|
||||
```tsx
|
||||
import { PageTitle } from "@common/components";
|
||||
import { useMenuStore } from "../lib/use-menu-store";
|
||||
|
||||
function PageLayout({ children }: { children: React.ReactNode }) {
|
||||
const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo);
|
||||
|
||||
const menuPaths = currentMenuInfo?.menuPath
|
||||
?.split(">")
|
||||
.map((path) => path.trim());
|
||||
|
||||
return (
|
||||
<div className="h-auto min-h-full bg-container pb-2">
|
||||
<PageTitle
|
||||
title={currentMenuInfo?.menuKornNm ?? ""}
|
||||
menuPaths={menuPaths ?? []}
|
||||
/>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 라우트 그룹의 작동 원리
|
||||
|
||||
### 1. URL 경로에 영향을 주지 않는 그룹화
|
||||
|
||||
Next.js App Router에서 괄호로 묶인 디렉토리는 URL 경로에 포함되지 않습니다. 예를 들어:
|
||||
|
||||
- `/bbs/register` 경로는 `(with-base-layout)/bbs/register` 디렉토리에 매핑됩니다.
|
||||
- `/user-survey-article/1` 경로는 `(without-base-layout)/user-survey-article/[srvySn]` 디렉토리에 매핑됩니다.
|
||||
|
||||
### 2. 레이아웃 분리 및 재사용
|
||||
|
||||
라우트 그룹을 사용하면 서로 다른 레이아웃을 가진 페이지들을 효율적으로 구성할 수 있습니다:
|
||||
|
||||
- `(with-base-layout)` 그룹: 메뉴, 헤더, 사이드바 등을 포함하는 `BaseLayout`과 `PageLayout`을 사용합니다.
|
||||
- `(without-base-layout)` 그룹: 기본 레이아웃 없이 페이지 컴포넌트만 렌더링합니다.
|
||||
|
||||
### 3. 중첩 라우트 그룹
|
||||
|
||||
프로젝트에서는 중첩된 라우트 그룹도 사용되고 있습니다:
|
||||
|
||||
```
|
||||
(with-base-layout)/bbs/(post)/bbsnormal/
|
||||
```
|
||||
|
||||
여기서 `(post)`는 게시물 관련 페이지들을 그룹화하는 중첩된 라우트 그룹입니다.
|
||||
|
||||
## 라우트 그룹 사용 사례
|
||||
|
||||
### 1. 관리자 인터페이스 (`with-base-layout`)
|
||||
|
||||
관리자 인터페이스는 일관된 레이아웃과 네비게이션이 필요합니다:
|
||||
|
||||
```tsx
|
||||
// (with-base-layout)/layout.tsx
|
||||
return (
|
||||
<BaseLayout
|
||||
queryClient={queryClient}
|
||||
currentPath={pathname ?? ""}
|
||||
handleMenuClick={handleMenuClick}
|
||||
>
|
||||
<PageLayout>{children}</PageLayout>
|
||||
</BaseLayout>
|
||||
);
|
||||
```
|
||||
|
||||
이 레이아웃은 다음과 같은 기능을 제공합니다:
|
||||
- 메뉴 시스템을 통한 페이지 간 이동
|
||||
- 현재 페이지 경로 표시
|
||||
- 일관된 페이지 타이틀 및 구조
|
||||
|
||||
### 2. 독립적인 페이지 (`without-base-layout`)
|
||||
|
||||
설문 기사나 결과 페이지와 같은 독립적인 페이지는 기본 레이아웃 없이 콘텐츠에 집중합니다:
|
||||
|
||||
```tsx
|
||||
// (without-base-layout)/user-survey-article/[srvySn]/page.tsx
|
||||
export default async function UserSurveyArticlePage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ srvySn: string }>;
|
||||
}) {
|
||||
const { srvySn } = await params;
|
||||
return <UserSurveyArticle srvySn={srvySn} />;
|
||||
}
|
||||
```
|
||||
|
||||
이러한 페이지는:
|
||||
- 외부 링크나 팝업으로 열리는 경우가 많음
|
||||
- 독립적인 기능을 제공(예: 설문 제출 후 창 닫기)
|
||||
- 메인 애플리케이션 UI와 분리된 경험 제공
|
||||
|
||||
## 두 라우트 그룹의 주요 차이점
|
||||
|
||||
### (with-base-layout)
|
||||
- `BaseLayout` 컴포넌트를 포함하는 레이아웃을 사용합니다.
|
||||
- 이 레이아웃은 메뉴, 헤더, 사이드바 등 기본 UI 요소를 포함합니다.
|
||||
- `PageLayout` 컴포넌트로 감싸져 있어 일관된 페이지 구조를 제공합니다.
|
||||
- 주로 관리자 페이지나 로그인 후 사용하는 내부 페이지에 사용됩니다.
|
||||
- 다양한 관리 기능을 포함하는 여러 하위 디렉토리가 있습니다.
|
||||
|
||||
### (without-base-layout)
|
||||
- 기본 레이아웃 없이 페이지 컴포넌트만 렌더링합니다.
|
||||
- 메뉴, 헤더, 사이드바 등의 UI 요소가 없는 독립적인 페이지입니다.
|
||||
- 주로 외부 사용자용 페이지나 팝업, 임베드 페이지에 사용됩니다.
|
||||
- 현재 두 가지 특수 목적 페이지만 포함하고 있습니다.
|
||||
|
||||
## 기술적 이점
|
||||
|
||||
1. **코드 구성 개선**: 관련 페이지와 레이아웃을 논리적으로 그룹화할 수 있습니다.
|
||||
2. **레이아웃 재사용**: 공통 레이아웃을 여러 페이지에서 재사용할 수 있습니다.
|
||||
3. **URL 구조 단순화**: 그룹 이름이 URL에 포함되지 않아 깔끔한 URL 구조를 유지할 수 있습니다.
|
||||
4. **유지보수성 향상**: 관련 기능을 함께 그룹화하여 코드 탐색과 유지보수가 용이합니다.
|
||||
|
||||
## 결론
|
||||
|
||||
Next.js App Router의 라우트 그룹 기능은 프로젝트에서 다양한 레이아웃 요구사항을 효율적으로 관리하는 데 중요한 역할을 합니다. `(with-base-layout)`과 `(without-base-layout)` 그룹을 통해 관리자 인터페이스와 독립적인 페이지를 명확하게 분리하면서도 코드 재사용성과 유지보수성을 높일 수 있습니다.
|
||||
|
||||
이 구조는 확장성이 뛰어나 새로운 페이지나 기능을 추가할 때 기존 패턴을 따르기 쉽고, 애플리케이션이 성장함에 따라 필요한 경우 추가 라우트 그룹을 도입할 수 있는 유연성을 제공합니다.
|
||||
@@ -0,0 +1,650 @@
|
||||
# 게시물 댓글(Comment) 관리 프로그램 아키텍처 문서
|
||||
|
||||
## 개요
|
||||
|
||||
이 문서는 게시물 댓글(Comment) 관리 프로그램의 아키텍처와 소스 흐름에 대한 상세 설명입니다. 댓글 관리 기능은 게시물에 대한 댓글을 등록, 조회, 수정, 삭제하는 기능을 제공하며, 로그인 및 비로그인 사용자 모두 이용할 수 있습니다.
|
||||
|
||||
## 기술 스택
|
||||
|
||||
- **프레임워크**: Next.js (App Router)
|
||||
- **상태 관리**: React Query, Zustand(useUserStore, useAlertStore)
|
||||
- **UI 컴포넌트**: @common/components
|
||||
- **폼 관리**: React Hook Form, Zod
|
||||
- **API 통신**: Axios
|
||||
- **알림 처리**: useConfirm, usePrompt 커스텀 훅
|
||||
|
||||
## 디렉토리 구조
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── apps/
|
||||
│ └── cs/
|
||||
│ ├── src/
|
||||
│ │ ├── entities/
|
||||
│ │ │ └── comment/
|
||||
│ │ │ ├── api/
|
||||
│ │ │ │ └── comment-service.ts # 댓글 API 호출 함수
|
||||
│ │ │ └── model/
|
||||
│ │ │ └── comment-interface.ts # 댓글 인터페이스 정의
|
||||
│ │ └── widgets/
|
||||
│ │ └── post/
|
||||
│ │ └── ui/
|
||||
│ │ └── post-comment.tsx # 댓글 UI 컴포넌트
|
||||
```
|
||||
|
||||
## 데이터 모델
|
||||
|
||||
### 댓글(Comment) 인터페이스
|
||||
|
||||
```typescript
|
||||
export interface Comment {
|
||||
cmntEvl: number; // 댓글 평가 점수
|
||||
pstNo: number; // 게시물 번호
|
||||
cmntNo: number; // 댓글 번호
|
||||
cmntCn: string; // 댓글 내용
|
||||
wrtrNm: string; // 작성자명
|
||||
regDt: string; // 등록일시
|
||||
rgtr: string; // 등록자
|
||||
mdfcnDt: string; // 수정일시
|
||||
mdfr: string; // 수정자
|
||||
delYn: string; // 삭제 여부
|
||||
}
|
||||
```
|
||||
|
||||
### 댓글 검색 파라미터(CommentParams) 인터페이스
|
||||
|
||||
```typescript
|
||||
export interface CommentParams extends PageableParams {
|
||||
keywordType?: string; // 검색 키워드 유형
|
||||
keyword?: string; // 검색 키워드
|
||||
pstNo: number; // 게시물 번호
|
||||
delYn?: string; // 삭제 여부
|
||||
}
|
||||
```
|
||||
|
||||
### 댓글 폼 데이터(CommentFormData) 인터페이스
|
||||
|
||||
Zod 스키마를 사용하여 폼 데이터의 유효성 검증을 수행합니다.
|
||||
|
||||
```typescript
|
||||
export const commentFormSchema = z.object({
|
||||
pstNo: z.number(), // 게시물 번호
|
||||
cmntCn: z.string(), // 댓글 내용
|
||||
wrtrNm: z.string().nullish(), // 작성자명 (선택적)
|
||||
cmntPswd: z.string(), // 댓글 비밀번호
|
||||
cmntEvl: z.number().nullish(), // 댓글 평가 점수 (선택적)
|
||||
});
|
||||
|
||||
export type CommentFormData = z.infer<typeof commentFormSchema>;
|
||||
```
|
||||
|
||||
### 댓글 수정 폼 데이터(CommentUpdateFormData) 인터페이스
|
||||
|
||||
```typescript
|
||||
export const commentUpdateFormSchema = commentFormSchema.omit({
|
||||
pstNo: true, // 수정 시에는 게시물 번호가 필요 없음
|
||||
});
|
||||
|
||||
export type CommentUpdateFormData = z.infer<typeof commentUpdateFormSchema>;
|
||||
```
|
||||
|
||||
## API 기능
|
||||
|
||||
댓글 관리 API는 다음과 같은 기능을 제공합니다:
|
||||
|
||||
### 1. 댓글 목록 조회
|
||||
|
||||
```typescript
|
||||
getComments: async (params: CommentParams): Promise<CommentList> => {
|
||||
const response = await client.get<CommentList>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 특정 게시물의 댓글 목록을 페이지네이션과 함께 조회합니다.
|
||||
- **파라미터**: `CommentParams` (게시물 번호, 검색 조건)
|
||||
- **반환값**: 페이지네이션된 댓글 목록 (`CommentList`)
|
||||
|
||||
### 2. 댓글 등록
|
||||
|
||||
```typescript
|
||||
createComment: async (data: CommentFormData): Promise<Comment> => {
|
||||
const validatedData = commentFormSchema.parse(data);
|
||||
const response = await client.post<Comment>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS}`,
|
||||
validatedData,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 새로운 댓글을 등록합니다.
|
||||
- **파라미터**: `CommentFormData` (댓글 등록 데이터)
|
||||
- **반환값**: 등록된 댓글 정보 (`Comment`)
|
||||
|
||||
### 3. 댓글 수정
|
||||
|
||||
```typescript
|
||||
updateComment: async (
|
||||
bbsId: number,
|
||||
pstNo: number,
|
||||
cmntNo: number,
|
||||
data: CommentUpdateFormData,
|
||||
): Promise<Comment> => {
|
||||
const validatedData = commentUpdateFormSchema.parse(data);
|
||||
const response = await client.put<Comment>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS}/${bbsId}/${pstNo}/${cmntNo}`,
|
||||
validatedData,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 기존 댓글 정보를 수정합니다.
|
||||
- **파라미터**: `bbsId` (게시판 ID), `pstNo` (게시물 번호), `cmntNo` (댓글 번호), `CommentUpdateFormData` (수정할 댓글 데이터)
|
||||
- **반환값**: 수정된 댓글 정보 (`Comment`)
|
||||
|
||||
### 4. 댓글 삭제
|
||||
|
||||
```typescript
|
||||
deleteComment: async (
|
||||
bbsId: number,
|
||||
pstNo: number,
|
||||
cmntNo: number,
|
||||
cmntPswd: string,
|
||||
): Promise<Comment> => {
|
||||
const response = await client.delete<Comment>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS}/${bbsId}/${pstNo}/${cmntNo}`,
|
||||
{ params: { cmntPswd } },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 댓글을 삭제합니다.
|
||||
- **파라미터**: `bbsId` (게시판 ID), `pstNo` (게시물 번호), `cmntNo` (댓글 번호), `cmntPswd` (댓글 비밀번호)
|
||||
- **반환값**: 삭제된 댓글 정보 (`Comment`)
|
||||
|
||||
## 프로그램 흐름
|
||||
|
||||
### 1. 댓글 목록 조회 흐름
|
||||
|
||||
1. 게시물 상세 페이지에서 `PostComment` 컴포넌트 로드
|
||||
2. React Query를 사용하여 `getComments` API 호출
|
||||
```typescript
|
||||
const { data: comments } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS, pstNo],
|
||||
queryFn: () => CommentApi.getComments({ pstNo }),
|
||||
enabled: !!pstNo,
|
||||
});
|
||||
```
|
||||
3. 응답 데이터를 UI에 표시
|
||||
```tsx
|
||||
{comments?.content.map((comment) => {
|
||||
if (comment.delYn === "Y") return null;
|
||||
return (
|
||||
<div className="flex justify-between gap-2 border-t border-gray-200 py-8" key={comment.cmntNo}>
|
||||
{/* 댓글 내용 표시 */}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
```
|
||||
|
||||
### 2. 댓글 등록 흐름
|
||||
|
||||
1. 사용자가 댓글 내용 입력 및 등록 버튼 클릭
|
||||
2. React Hook Form을 통해 폼 데이터 유효성 검증
|
||||
```typescript
|
||||
const form = useForm<CommentFormData>({
|
||||
resolver: zodResolver(commentFormSchema),
|
||||
values: {
|
||||
pstNo,
|
||||
cmntCn: "",
|
||||
wrtrNm: userInfo?.userName ?? "",
|
||||
cmntPswd: userInfo?.userUnqId ?? "",
|
||||
},
|
||||
});
|
||||
```
|
||||
3. 유효성 검증 통과 시 `onSubmit` 함수 호출
|
||||
```typescript
|
||||
const onSubmit = (data: CommentFormData) => {
|
||||
createComment(data);
|
||||
};
|
||||
```
|
||||
4. React Query의 `useMutation` 훅을 통해 `createComment` API 호출
|
||||
```typescript
|
||||
const { mutate: createComment } = useMutation({
|
||||
mutationFn: (data: CommentFormData) => CommentApi.createComment(data),
|
||||
onSuccess: () => {
|
||||
alert("등록되었습니다.");
|
||||
form.setValue("cmntCn", "");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
|
||||
});
|
||||
},
|
||||
});
|
||||
```
|
||||
5. 성공 시 알림 표시 및 댓글 목록 새로고침
|
||||
|
||||
### 3. 댓글 수정 흐름
|
||||
|
||||
1. 사용자가 댓글 수정 버튼 클릭
|
||||
2. 수정 모드 활성화 및 기존 댓글 내용 로드
|
||||
```typescript
|
||||
const handleEdit = (cmntNo: number, cmntCn: string) => {
|
||||
setEditingCmntNo(cmntNo);
|
||||
setEditValue(cmntCn);
|
||||
};
|
||||
```
|
||||
3. 사용자가 댓글 내용 수정 후 저장 버튼 클릭
|
||||
4. 확인 다이얼로그 표시 및 비밀번호 확인
|
||||
```typescript
|
||||
const handleUpdateComment = async (cmntNo: number, wrtrNm: string) => {
|
||||
if (!(await confirm("수정하시겠습니까?"))) return;
|
||||
const cmntPswd = await getCommentPassword();
|
||||
updateComment({
|
||||
cmntNo,
|
||||
data: {
|
||||
cmntCn: editValue,
|
||||
wrtrNm,
|
||||
cmntPswd,
|
||||
},
|
||||
});
|
||||
};
|
||||
```
|
||||
5. React Query의 `useMutation` 훅을 통해 `updateComment` API 호출
|
||||
6. 성공 시 알림 표시 및 댓글 목록 새로고침
|
||||
|
||||
### 4. 댓글 삭제 흐름
|
||||
|
||||
1. 사용자가 댓글 삭제 버튼 클릭
|
||||
2. 확인 다이얼로그 표시 및 비밀번호 확인
|
||||
```typescript
|
||||
const handleDeleteComment = async (cmntNo: number) => {
|
||||
if (!(await confirm("삭제하시겠습니까?"))) return;
|
||||
const cmntPswd = await getCommentPassword();
|
||||
deleteComment({ cmntNo, cmntPswd });
|
||||
};
|
||||
```
|
||||
3. React Query의 `useMutation` 훅을 통해 `deleteComment` API 호출
|
||||
4. 성공 시 알림 표시 및 댓글 목록 새로고침
|
||||
|
||||
## 주요 컴포넌트
|
||||
|
||||
### PostComment 컴포넌트
|
||||
|
||||
게시물 댓글을 표시하고 관리하는 컴포넌트입니다.
|
||||
|
||||
```tsx
|
||||
function PostComment({ bbsId, pstNo }: { bbsId: number; pstNo: number }) {
|
||||
// 상태 및 훅 설정
|
||||
const { prompt } = usePrompt();
|
||||
const { confirm } = useConfirm();
|
||||
const queryClient = useQueryClient();
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [editingCmntNo, setEditingCmntNo] = useState<number | null>(null);
|
||||
const [editValue, setEditValue] = useState<string>("");
|
||||
|
||||
// 댓글 목록 조회
|
||||
const { data: comments } = useQuery({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS, pstNo],
|
||||
queryFn: () => CommentApi.getComments({ pstNo }),
|
||||
enabled: !!pstNo,
|
||||
});
|
||||
|
||||
// 댓글 등록 뮤테이션
|
||||
const { mutate: createComment } = useMutation({
|
||||
mutationFn: (data: CommentFormData) => CommentApi.createComment(data),
|
||||
onSuccess: () => {
|
||||
alert("등록되었습니다.");
|
||||
form.setValue("cmntCn", "");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 댓글 수정 뮤테이션
|
||||
const { mutate: updateComment } = useMutation({
|
||||
mutationFn: ({
|
||||
cmntNo,
|
||||
data,
|
||||
}: {
|
||||
cmntNo: number;
|
||||
data: CommentUpdateFormData;
|
||||
}) => CommentApi.updateComment(bbsId, pstNo, cmntNo, data),
|
||||
onSuccess: () => {
|
||||
alert("수정되었습니다.");
|
||||
setEditingCmntNo(null);
|
||||
setEditValue("");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 댓글 삭제 뮤테이션
|
||||
const { mutate: deleteComment } = useMutation({
|
||||
mutationFn: ({ cmntNo, cmntPswd }: { cmntNo: number; cmntPswd: string }) =>
|
||||
CommentApi.deleteComment(bbsId, pstNo, cmntNo, cmntPswd),
|
||||
onSuccess: () => {
|
||||
alert("삭제되었습니다.");
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_COMMENTS],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 폼 설정
|
||||
const form = useForm<CommentFormData>({
|
||||
resolver: zodResolver(commentFormSchema),
|
||||
values: {
|
||||
pstNo,
|
||||
cmntCn: "",
|
||||
wrtrNm: userInfo?.userName ?? "",
|
||||
cmntPswd: userInfo?.userUnqId ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
// 폼 제출 핸들러
|
||||
const onSubmit = (data: CommentFormData) => {
|
||||
createComment(data);
|
||||
};
|
||||
|
||||
// 비밀번호 획득 함수
|
||||
const getCommentPassword = async () => {
|
||||
let cmntPswd = "";
|
||||
if (userInfo?.userUnqId) {
|
||||
cmntPswd = userInfo?.userUnqId;
|
||||
} else {
|
||||
const { resolved, value } = await prompt("비밀번호를 입력해주세요.");
|
||||
if (resolved) cmntPswd = value ?? "";
|
||||
}
|
||||
return cmntPswd;
|
||||
};
|
||||
|
||||
// 댓글 수정 핸들러
|
||||
const handleUpdateComment = async (cmntNo: number, wrtrNm: string) => {
|
||||
if (!(await confirm("수정하시겠습니까?"))) return;
|
||||
const cmntPswd = await getCommentPassword();
|
||||
updateComment({
|
||||
cmntNo,
|
||||
data: {
|
||||
cmntCn: editValue,
|
||||
wrtrNm,
|
||||
cmntPswd,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 댓글 삭제 핸들러
|
||||
const handleDeleteComment = async (cmntNo: number) => {
|
||||
if (!(await confirm("삭제하시겠습니까?"))) return;
|
||||
const cmntPswd = await getCommentPassword();
|
||||
deleteComment({ cmntNo, cmntPswd });
|
||||
};
|
||||
|
||||
// UI 렌더링
|
||||
return (
|
||||
<div className="card card-border !p-8">
|
||||
{/* 댓글 등록 폼 */}
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)}>
|
||||
{/* 댓글 내용 입력 필드 */}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cmntCn"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
maxLength={1000}
|
||||
className="h-[10rem] bg-background p-4 !text-[1.3rem] !leading-6"
|
||||
placeholder="댓글을 입력해주세요."
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<div className="flex gap-4">
|
||||
{/* 비로그인 사용자를 위한 작성자명 입력 필드 */}
|
||||
{!userInfo?.userName && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="wrtrNm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="w-[16rem] bg-background !text-[1.3rem]"
|
||||
placeholder="작성자"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
{/* 비로그인 사용자를 위한 비밀번호 입력 필드 */}
|
||||
{!userInfo?.userUnqId && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cmntPswd"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
value={field.value ?? ""}
|
||||
className="w-[12rem] bg-background !text-[1.3rem]"
|
||||
placeholder="비밀번호"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Button type="submit">등록</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
{/* 댓글 목록 */}
|
||||
<div className="mt-8 flex flex-col">
|
||||
{comments?.content.map((comment) => {
|
||||
if (comment.delYn === "Y") return null;
|
||||
return (
|
||||
<div
|
||||
className="flex justify-between gap-2 border-t border-gray-200 py-8"
|
||||
key={comment.cmntNo}
|
||||
>
|
||||
{/* 댓글 정보 표시 */}
|
||||
<div className="flex w-full gap-10">
|
||||
<div className="flex w-[10rem] shrink-0 flex-col">
|
||||
<span className="text-[1.4rem] font-bold text-label">
|
||||
{comment.wrtrNm}
|
||||
</span>
|
||||
<span className="text-[1.2rem] text-label">
|
||||
{dayjs(comment.regDt).format("YYYY.MM.DD")}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* 수정 모드일 때 */}
|
||||
{editingCmntNo === comment.cmntNo ? (
|
||||
<div className="flex flex-1 flex-col gap-2">
|
||||
<Textarea
|
||||
value={editValue}
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
className="h-[10rem] bg-background p-4 !text-[1.3rem] !leading-6"
|
||||
/>
|
||||
<div className="flex justify-end gap-2">
|
||||
<Button
|
||||
onClick={() => {
|
||||
setEditingCmntNo(null);
|
||||
setEditValue("");
|
||||
}}
|
||||
>
|
||||
취소
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() =>
|
||||
handleUpdateComment(comment.cmntNo, comment.wrtrNm)
|
||||
}
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
/* 일반 모드일 때 */
|
||||
<div className="flex flex-1 flex-col">
|
||||
<p className="whitespace-pre-wrap text-[1.4rem] text-label">
|
||||
{comment.cmntCn}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 수정/삭제 버튼 */}
|
||||
{editingCmntNo !== comment.cmntNo && (
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => handleEdit(comment.cmntNo, comment.cmntCn)}
|
||||
>
|
||||
수정
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
color="red"
|
||||
onClick={() => handleDeleteComment(comment.cmntNo)}
|
||||
>
|
||||
삭제
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
## 로그인/비로그인 사용자 처리
|
||||
|
||||
### 로그인 사용자 처리
|
||||
|
||||
로그인 사용자의 경우 `useUserStore`에서 사용자 정보를 가져와 자동으로 설정합니다.
|
||||
|
||||
```typescript
|
||||
const { userInfo } = useUserStore((state) => state);
|
||||
|
||||
const form = useForm<CommentFormData>({
|
||||
resolver: zodResolver(commentFormSchema),
|
||||
values: {
|
||||
pstNo,
|
||||
cmntCn: "",
|
||||
wrtrNm: userInfo?.userName ?? "",
|
||||
cmntPswd: userInfo?.userUnqId ?? "",
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 비로그인 사용자 처리
|
||||
|
||||
비로그인 사용자의 경우 작성자명과 비밀번호를 직접 입력받습니다.
|
||||
|
||||
```tsx
|
||||
{!userInfo?.userName && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="wrtrNm"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
value={field.value ?? ""}
|
||||
className="w-[16rem] bg-background !text-[1.3rem]"
|
||||
placeholder="작성자"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{!userInfo?.userUnqId && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="cmntPswd"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
value={field.value ?? ""}
|
||||
className="w-[12rem] bg-background !text-[1.3rem]"
|
||||
placeholder="비밀번호"
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
```
|
||||
|
||||
## 비밀번호 검증 로직
|
||||
|
||||
댓글 수정/삭제 시 비밀번호 검증을 위한 로직이 구현되어 있습니다.
|
||||
|
||||
```typescript
|
||||
const getCommentPassword = async () => {
|
||||
let cmntPswd = "";
|
||||
if (userInfo?.userUnqId) {
|
||||
// 로그인 사용자의 경우 사용자 ID를 비밀번호로 사용
|
||||
cmntPswd = userInfo?.userUnqId;
|
||||
} else {
|
||||
// 비로그인 사용자의 경우 프롬프트를 통해 비밀번호 입력 요청
|
||||
const { resolved, value } = await prompt("비밀번호를 입력해주세요.");
|
||||
if (resolved) cmntPswd = value ?? "";
|
||||
}
|
||||
return cmntPswd;
|
||||
};
|
||||
```
|
||||
|
||||
## 아키텍처 특징
|
||||
|
||||
1. **계층 분리**: API, 모델, UI 컴포넌트가 명확히 분리되어 있습니다.
|
||||
2. **타입 안전성**: TypeScript 인터페이스와 Zod 스키마를 통한 타입 검증을 제공합니다.
|
||||
3. **상태 관리**: React Query를 통한 서버 상태 관리와 Zustand를 통한 클라이언트 상태 관리를 제공합니다.
|
||||
4. **유효성 검증**: Zod 스키마를 사용하여 폼 데이터의 유효성을 검증합니다.
|
||||
5. **사용자 경험**: 로그인/비로그인 사용자에 대한 차별화된 UI 제공합니다.
|
||||
6. **비동기 처리**: React Query의 `useMutation`을 통한 효율적인 비동기 작업 처리를 제공합니다.
|
||||
|
||||
## 확장 가능성
|
||||
|
||||
1. **댓글 계층 구조**: 대댓글 기능 추가
|
||||
2. **댓글 좋아요/싫어요**: 댓글 평가 기능 강화
|
||||
3. **댓글 신고**: 부적절한 댓글 신고 기능 추가
|
||||
4. **댓글 알림**: 댓글 등록 시 알림 기능 추가
|
||||
5. **댓글 정렬**: 최신순, 인기순 등 다양한 정렬 옵션 제공
|
||||
6. **댓글 검색**: 댓글 내용 검색 기능 추가
|
||||
|
||||
## 주의사항
|
||||
|
||||
1. 비로그인 사용자의 댓글 비밀번호는 클라이언트 측에서만 검증되므로 보안에 주의가 필요합니다.
|
||||
2. 댓글 삭제 시 실제로 데이터베이스에서 삭제하지 않고 `delYn` 필드를 'Y'로 설정하는 소프트 삭제 방식을 사용합니다.
|
||||
3. 댓글 내용에 HTML 태그나 스크립트가 포함될 경우 XSS 공격 가능성이 있으므로 서버 측에서 적절한 필터링이 필요합니다.
|
||||
@@ -0,0 +1,480 @@
|
||||
# 게시물(Post) 관리 프로그램 아키텍처 문서
|
||||
|
||||
## 개요
|
||||
|
||||
이 문서는 게시물(Post) 관리 프로그램의 아키텍처와 소스 흐름에 대한 상세 설명입니다. 게시물 관리 기능은 게시판에 게시물을 등록, 조회, 수정, 삭제하는 기능을 제공하며, 다양한 유형의 게시물(일반, 이미지, 동영상, 공지사항 등)을 지원합니다.
|
||||
|
||||
## 기술 스택
|
||||
|
||||
- **프레임워크**: Next.js (App Router)
|
||||
- **상태 관리**: React Query, Zustand(useMenuStore, useUserStore)
|
||||
- **UI 컴포넌트**: @common/components
|
||||
- **테이블 관리**: TanStack Table
|
||||
- **폼 관리**: React Hook Form, Zod
|
||||
- **API 통신**: Axios
|
||||
- **파일 업로드**: 커스텀 파일 업로더 컴포넌트
|
||||
|
||||
## 디렉토리 구조
|
||||
|
||||
```
|
||||
frontend/
|
||||
├── apps/
|
||||
│ └── cs/
|
||||
│ ├── app/
|
||||
│ │ └── (with-base-layout)/
|
||||
│ │ └── bbs/
|
||||
│ │ └── (post)/ # 게시물 관련 페이지
|
||||
│ ├── src/
|
||||
│ │ ├── entities/
|
||||
│ │ │ ├── post/
|
||||
│ │ │ │ ├── api/
|
||||
│ │ │ │ │ └── post-service.ts # 게시물 API 호출 함수
|
||||
│ │ │ │ └── model/
|
||||
│ │ │ │ └── post-interface.ts # 게시물 인터페이스 정의
|
||||
│ │ │ └── posting/
|
||||
│ │ │ ├── api/
|
||||
│ │ │ │ └── posting-service.ts # 게시 관련 API 호출 함수
|
||||
│ │ │ ├── model/
|
||||
│ │ │ │ └── posting-interface.ts # 게시 관련 인터페이스 정의
|
||||
│ │ │ └── ui/
|
||||
│ │ │ └── posting-dialog.tsx # 게시 관련 다이얼로그 컴포넌트
|
||||
│ │ ├── pages/
|
||||
│ │ │ └── (post)/
|
||||
│ │ │ ├── image/
|
||||
│ │ │ │ └── ui/
|
||||
│ │ │ │ └── image-post.tsx # 이미지 게시물 컴포넌트
|
||||
│ │ │ ├── movie/
|
||||
│ │ │ │ └── ui/
|
||||
│ │ │ │ └── movie-post.tsx # 동영상 게시물 컴포넌트
|
||||
│ │ │ ├── normal/
|
||||
│ │ │ │ └── ui/
|
||||
│ │ │ │ └── normal-post.tsx # 일반 게시물 컴포넌트
|
||||
│ │ │ └── notice/
|
||||
│ │ │ └── ui/
|
||||
│ │ │ └── notice-post.tsx # 공지사항 게시물 컴포넌트
|
||||
│ │ └── widgets/
|
||||
│ │ └── post/
|
||||
│ │ ├── model/
|
||||
│ │ │ ├── post-table-columns.tsx # 게시물 테이블 컬럼 정의
|
||||
│ │ │ └── grid-view-table-columns.tsx # 그리드 뷰 테이블 컬럼 정의
|
||||
│ │ └── ui/
|
||||
│ │ ├── post-action.tsx # 게시물 액션 컴포넌트
|
||||
│ │ ├── post-comment.tsx # 게시물 댓글 컴포넌트
|
||||
│ │ ├── post-form.tsx # 게시물 등록/수정 폼
|
||||
│ │ ├── post-page.tsx # 게시물 목록 페이지
|
||||
│ │ ├── post.tsx # 게시물 상세 컴포넌트
|
||||
│ │ └── posting-period-selector.tsx # 게시 기간 선택기
|
||||
```
|
||||
|
||||
## 데이터 모델
|
||||
|
||||
### 게시물(Post) 인터페이스
|
||||
|
||||
```typescript
|
||||
export interface Post {
|
||||
rowNo: number; // 행 번호
|
||||
bbsId: number; // 게시판 ID
|
||||
pstNo: number; // 게시물 번호
|
||||
pstTtl: string; // 게시물 제목
|
||||
pstCn: string; // 게시물 내용
|
||||
inqNocs: number; // 조회수
|
||||
rfrncUrl: string; // 참조 URL
|
||||
popupYn: string; // 팝업 여부
|
||||
upndFixYn: string; // 상단 고정 여부
|
||||
prdPstgYn: string; // 기간 게시 여부
|
||||
rootPath: string; // 루트 경로
|
||||
pstPath: string; // 게시물 경로
|
||||
ansPstn: number; // 답변 위치
|
||||
wrtrNm: string; // 작성자명
|
||||
wrtrCplc: string; // 작성자 소속
|
||||
atflCd: string; // 첨부파일 코드
|
||||
regDt: string; // 등록일시
|
||||
rgtrNm: string; // 등록자명
|
||||
cmntCnt: number; // 댓글 수
|
||||
}
|
||||
```
|
||||
|
||||
### 게시물 상세 정보(PostInfo) 인터페이스
|
||||
|
||||
```typescript
|
||||
export interface PostInfo extends Post {
|
||||
pstCn: string; // 게시물 내용
|
||||
rgtr: string; // 등록자
|
||||
mdfcnDt: string; // 수정일시
|
||||
mdfr: string; // 수정자
|
||||
}
|
||||
```
|
||||
|
||||
### 최근 게시물(RecentPost) 인터페이스
|
||||
|
||||
```typescript
|
||||
export interface RecentPost extends Post {
|
||||
bbsNm: string; // 게시판명
|
||||
bbsTypeCd: string; // 게시판 유형 코드
|
||||
}
|
||||
```
|
||||
|
||||
### 게시물 이동(MovePost) 인터페이스
|
||||
|
||||
```typescript
|
||||
export interface MovePost {
|
||||
fromBbsId: number; // 출발 게시판 ID
|
||||
toBbsId?: number; // 도착 게시판 ID
|
||||
pstNo: number; // 게시물 번호
|
||||
}
|
||||
```
|
||||
|
||||
### 게시물 삭제(DeletePost) 인터페이스
|
||||
|
||||
```typescript
|
||||
export interface DeletePost {
|
||||
bbsId: number; // 게시판 ID
|
||||
pstNo: number; // 게시물 번호
|
||||
delRsn?: string; // 삭제 사유
|
||||
}
|
||||
```
|
||||
|
||||
### 게시물 검색 파라미터(PostParams) 인터페이스
|
||||
|
||||
```typescript
|
||||
export interface PostParams extends PageableParams {
|
||||
keywordType?: string; // 검색 키워드 유형 (제목, 내용, 작성자 등)
|
||||
keyword?: string; // 검색 키워드
|
||||
popupYn?: string; // 팝업 여부
|
||||
upndFixYn?: string; // 상단 고정 여부
|
||||
prdPstgYn?: string; // 기간 게시 여부
|
||||
siteId?: string; // 사이트 ID
|
||||
bbsTypeCd?: string; // 게시판 유형 코드
|
||||
}
|
||||
```
|
||||
|
||||
### 최근 게시물 검색 파라미터(RecentPostParams) 인터페이스
|
||||
|
||||
```typescript
|
||||
export interface RecentPostParams extends PageableParams {
|
||||
keywordType?: string; // 검색 키워드 유형
|
||||
keyword?: string; // 검색 키워드
|
||||
bbsId?: number | null; // 게시판 ID
|
||||
delYn?: string; // 삭제 여부
|
||||
frDateYmd?: string; // 시작 날짜
|
||||
toDateYmd?: string; // 종료 날짜
|
||||
}
|
||||
```
|
||||
|
||||
### 게시물 폼 데이터(PostFormData) 인터페이스
|
||||
|
||||
Zod 스키마를 사용하여 폼 데이터의 유효성 검증을 수행합니다.
|
||||
|
||||
```typescript
|
||||
export const postFormSchema = z.object({
|
||||
bbsId: z.number(), // 게시판 ID
|
||||
pstTtl: z.string().nonempty(), // 게시물 제목
|
||||
pstCn: z.string().nullish(), // 게시물 내용
|
||||
rfrncUrl: z.string().nullish(), // 참조 URL
|
||||
popupYn: z.string().nullish(), // 팝업 여부
|
||||
popupYnEtc: z.array(postEtcSchema).nullish(), // 팝업 관련 추가 정보
|
||||
upndFixYn: z.string().nullish(), // 상단 고정 여부
|
||||
upndFixYnEtc: z.array(postEtcSchema).nullish(), // 상단 고정 관련 추가 정보
|
||||
prdPstgYn: z.string().nullish(), // 기간 게시 여부
|
||||
prdPstgYnEtc: z.array(postEtcSchema).nullish(), // 기간 게시 관련 추가 정보
|
||||
pstPswd: z.string(), // 게시물 비밀번호
|
||||
wrtrNm: z.string().nullish(), // 작성자명
|
||||
wrtrCplc: z.string().nullish(), // 작성자 소속
|
||||
atflCd: z.string().nullish(), // 첨부파일 코드
|
||||
parentPstNo: z.number().nullish(), // 부모 게시물 번호
|
||||
});
|
||||
```
|
||||
|
||||
## API 기능
|
||||
|
||||
게시물 관리 API는 다음과 같은 기능을 제공합니다:
|
||||
|
||||
### 1. 게시물 목록 조회
|
||||
|
||||
```typescript
|
||||
getPostList: async (bbsId: number, params: PostParams): Promise<PostList> => {
|
||||
const response = await client.get<PostList>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 특정 게시판의 게시물 목록을 페이지네이션과 함께 조회합니다.
|
||||
- **파라미터**: `bbsId` (게시판 ID), `PostParams` (검색 조건)
|
||||
- **반환값**: 페이지네이션된 게시물 목록 (`PostList`)
|
||||
|
||||
### 2. 최근 게시물 목록 조회
|
||||
|
||||
```typescript
|
||||
getRecentPostList: async (params?: RecentPostParams): Promise<RecentPostList> => {
|
||||
const response = await client.get<RecentPostList>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}/recentPosts`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 최근 게시물 목록을 조회합니다.
|
||||
- **파라미터**: `RecentPostParams` (검색 조건)
|
||||
- **반환값**: 페이지네이션된 최근 게시물 목록 (`RecentPostList`)
|
||||
|
||||
### 3. 게시물 상세 조회
|
||||
|
||||
```typescript
|
||||
getPostInfo: async (bbsId: number, pstNo: number): Promise<PostInfo> => {
|
||||
const response = await client.get<PostInfo>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 특정 게시물의 상세 정보를 조회합니다.
|
||||
- **파라미터**: `bbsId` (게시판 ID), `pstNo` (게시물 번호)
|
||||
- **반환값**: 게시물 상세 정보 (`PostInfo`)
|
||||
|
||||
### 4. 게시물 생성
|
||||
|
||||
```typescript
|
||||
createPost: async (data: PostFormData): Promise<Post> => {
|
||||
const validatedData = postFormSchema.parse(data);
|
||||
const response = await client.post<Post>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}`,
|
||||
validatedData,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 새로운 게시물을 생성합니다.
|
||||
- **파라미터**: `PostFormData` (게시물 생성 데이터)
|
||||
- **반환값**: 생성된 게시물 정보 (`Post`)
|
||||
|
||||
### 5. 게시물 수정
|
||||
|
||||
```typescript
|
||||
updatePost: async (bbsId: number, pstNo: number, data: PostUpdateFormData): Promise<Post> => {
|
||||
const validatedData = postUpdateFormSchema.parse(data);
|
||||
const response = await client.put<Post>(
|
||||
`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`,
|
||||
validatedData,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 기존 게시물 정보를 수정합니다.
|
||||
- **파라미터**: `bbsId` (게시판 ID), `pstNo` (게시물 번호), `PostUpdateFormData` (수정할 게시물 데이터)
|
||||
- **반환값**: 수정된 게시물 정보 (`Post`)
|
||||
|
||||
### 6. 게시물 삭제
|
||||
|
||||
```typescript
|
||||
deletePost: async (bbsId: number, pstNo: number, delRsn?: string): Promise<void> => {
|
||||
await client.delete(`${ENDPOINT.BOARD_SERVICE.POSTS}/${bbsId}/${pstNo}`, {
|
||||
params: { delRsn },
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 게시물을 삭제합니다.
|
||||
- **파라미터**: `bbsId` (게시판 ID), `pstNo` (게시물 번호), `delRsn` (삭제 사유, 선택적)
|
||||
- **반환값**: void
|
||||
|
||||
### 7. 게시물 소프트 삭제 (여러 게시물)
|
||||
|
||||
```typescript
|
||||
softDeletePost: async (data: DeletePost[]): Promise<void> => {
|
||||
await client.post(`${ENDPOINT.BOARD_SERVICE.POSTS}/softDeleteSels`, data);
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 여러 게시물을 소프트 삭제합니다 (실제 삭제하지 않고 삭제 표시만 함).
|
||||
- **파라미터**: `DeletePost[]` (삭제할 게시물 목록)
|
||||
- **반환값**: void
|
||||
|
||||
### 8. 게시물 하드 삭제 (여러 게시물)
|
||||
|
||||
```typescript
|
||||
hardDeletePost: async (data: DeletePost[]): Promise<void> => {
|
||||
await client.post(`${ENDPOINT.BOARD_SERVICE.POSTS}/hardDeleteSels`, data);
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 여러 게시물을 하드 삭제합니다 (실제로 데이터베이스에서 삭제).
|
||||
- **파라미터**: `DeletePost[]` (삭제할 게시물 목록)
|
||||
- **반환값**: void
|
||||
|
||||
### 9. 게시물 이동
|
||||
|
||||
```typescript
|
||||
movePost: async (data: MovePost[]): Promise<void> => {
|
||||
await client.post(`${ENDPOINT.BOARD_SERVICE.POSTS}/movePstByBbsId`, data);
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 게시물을 다른 게시판으로 이동합니다.
|
||||
- **파라미터**: `MovePost[]` (이동할 게시물 정보 목록)
|
||||
- **반환값**: void
|
||||
|
||||
### 10. 게시물 복구
|
||||
|
||||
```typescript
|
||||
restorePost: async (data: DeletePost[]): Promise<void> => {
|
||||
await client.post(`${ENDPOINT.BOARD_SERVICE.POSTS}/restoreSels`, data);
|
||||
}
|
||||
```
|
||||
|
||||
- **설명**: 소프트 삭제된 게시물을 복구합니다.
|
||||
- **파라미터**: `DeletePost[]` (복구할 게시물 목록)
|
||||
- **반환값**: void
|
||||
|
||||
## 프로그램 흐름
|
||||
|
||||
### 1. 게시물 목록 조회 흐름
|
||||
|
||||
1. 사용자가 게시판 페이지에 접근
|
||||
2. `useMenuStore`를 통해 현재 게시판 정보 로드
|
||||
3. React Query를 사용하여 `getPostList` API 호출
|
||||
4. 상단 고정 게시물이 있는 경우 별도로 조회하여 목록 상단에 표시
|
||||
5. 검색 조건 변경 시 쿼리 파라미터 업데이트 및 API 재호출
|
||||
6. 응답 데이터를 TanStack Table을 사용하여 테이블 형태로 표시
|
||||
7. 게시판 유형에 따라 일반 테이블 또는 그리드 뷰로 표시
|
||||
|
||||
### 2. 게시물 상세 조회 흐름
|
||||
|
||||
1. 사용자가 게시물 목록에서 특정 게시물 클릭
|
||||
2. 해당 게시물의 상세 페이지로 이동 (`/bbs/{bbsId}/{pstNo}`)
|
||||
3. React Query를 사용하여 `getPostInfo` API 호출
|
||||
4. 게시물 상세 정보 및 첨부파일 정보 로드
|
||||
5. 게시판 유형에 따라 적절한 상세 보기 컴포넌트 렌더링
|
||||
- 일반 게시물: `normal-post.tsx`
|
||||
- 이미지 게시물: `image-post.tsx`
|
||||
- 동영상 게시물: `movie-post.tsx`
|
||||
- 공지사항: `notice-post.tsx`
|
||||
|
||||
### 3. 게시물 등록 흐름
|
||||
|
||||
1. 사용자가 게시물 등록 버튼 클릭
|
||||
2. 게시물 등록 페이지로 이동 (`/bbs/{bbsId}/register`)
|
||||
3. React Hook Form과 Zod를 사용하여 폼 데이터 관리 및 유효성 검증
|
||||
4. 첨부파일 업로드 시 `AttachmentApi`를 통해 파일 업로드 및 `atflCd` 획득
|
||||
5. 저장 버튼 클릭 시 확인 다이얼로그 표시 후 `createPost` API 호출
|
||||
6. 성공 시 게시물 목록 페이지로 리다이렉트
|
||||
|
||||
### 4. 게시물 수정 흐름
|
||||
|
||||
1. 사용자가 게시물 상세 페이지에서 수정 버튼 클릭
|
||||
2. 게시물 수정 페이지로 이동 (`/bbs/{bbsId}/{pstNo}/modify`)
|
||||
3. `getPostInfo` API를 호출하여 기존 게시물 데이터 로드
|
||||
4. 기존 첨부파일 정보 로드 및 표시
|
||||
5. 사용자가 데이터 수정 후 저장 버튼 클릭
|
||||
6. 확인 다이얼로그 표시 후 `updatePost` API 호출
|
||||
7. 성공 시 게시물 상세 페이지로 리다이렉트
|
||||
|
||||
### 5. 게시물 삭제 흐름
|
||||
|
||||
1. 사용자가 게시물 상세 페이지에서 삭제 버튼 클릭 또는 목록에서 여러 게시물 선택 후 삭제 버튼 클릭
|
||||
2. 삭제 사유 입력 다이얼로그 표시
|
||||
3. 사용자가 삭제 사유 입력 후 확인
|
||||
4. 단일 게시물 삭제 시 `deletePost` API 호출, 여러 게시물 삭제 시 `softDeletePost` 또는 `hardDeletePost` API 호출
|
||||
5. 성공 시 게시물 목록 페이지로 리다이렉트 또는 목록 새로고침
|
||||
|
||||
## 주요 컴포넌트
|
||||
|
||||
### 1. PostPage 컴포넌트
|
||||
|
||||
게시물 목록을 조회하고 표시하는 페이지 컴포넌트입니다.
|
||||
|
||||
- **기능**:
|
||||
- 게시물 검색 (제목, 내용, 작성자 등)
|
||||
- 페이지네이션
|
||||
- 페이지 크기 조정
|
||||
- 게시물 목록 표시 (테이블 또는 그리드 뷰)
|
||||
- 게시물 등록 페이지로 이동
|
||||
- 게시물 상세 페이지로 이동 (행 클릭)
|
||||
- 선택한 게시물 삭제
|
||||
|
||||
### 2. PostForm 컴포넌트
|
||||
|
||||
게시물 등록 및 수정에 사용되는 폼 컴포넌트입니다.
|
||||
|
||||
- **기능**:
|
||||
- 게시물 정보 입력/수정 (제목, 내용, 작성자 등)
|
||||
- 첨부파일 업로드 및 관리
|
||||
- 참조 URL 입력 (게시판 설정에 따라)
|
||||
- 팝업 게시 설정 (게시판 설정에 따라)
|
||||
- 상단 고정 설정 (게시판 설정에 따라)
|
||||
- 기간 게시 설정 (게시판 설정에 따라)
|
||||
- 저장 확인 다이얼로그
|
||||
- 취소 기능
|
||||
|
||||
### 3. Post 컴포넌트
|
||||
|
||||
게시물 상세 정보를 표시하는 컴포넌트입니다.
|
||||
|
||||
- **기능**:
|
||||
- 게시물 상세 정보 표시
|
||||
- 첨부파일 다운로드
|
||||
- 수정/삭제 버튼
|
||||
- 목록으로 돌아가기
|
||||
- 댓글 표시 및 관리 (게시판 설정에 따라)
|
||||
|
||||
### 4. PostComment 컴포넌트
|
||||
|
||||
게시물 댓글을 표시하고 관리하는 컴포넌트입니다.
|
||||
|
||||
- **기능**:
|
||||
- 댓글 목록 표시
|
||||
- 댓글 등록
|
||||
- 댓글 수정/삭제
|
||||
- 댓글 페이지네이션
|
||||
|
||||
### 5. PostingPeriodSelector 컴포넌트
|
||||
|
||||
게시 기간을 선택하는 컴포넌트입니다.
|
||||
|
||||
- **기능**:
|
||||
- 게시 기간 유형 선택
|
||||
- 시작일/종료일 선택
|
||||
- 게시 기간 설정
|
||||
|
||||
### 6. 게시물 유형별 컴포넌트
|
||||
|
||||
게시판 유형에 따라 다양한 게시물 컴포넌트를 제공합니다.
|
||||
|
||||
- **NormalPost**: 일반 텍스트 게시물 컴포넌트
|
||||
- **ImagePost**: 이미지 게시물 컴포넌트 (이미지 갤러리 형태)
|
||||
- **MoviePost**: 동영상 게시물 컴포넌트 (동영상 플레이어 포함)
|
||||
- **NoticePost**: 공지사항 게시물 컴포넌트
|
||||
|
||||
## 아키텍처 특징
|
||||
|
||||
1. **계층 분리**: API, 모델, UI 컴포넌트가 명확히 분리되어 있습니다.
|
||||
2. **타입 안전성**: TypeScript 인터페이스와 Zod 스키마를 통한 타입 검증을 제공합니다.
|
||||
3. **컴포넌트 재사용**: 공통 컴포넌트를 활용하여 코드 중복을 최소화합니다.
|
||||
4. **유효성 검증**: Zod 스키마를 사용하여 폼 데이터의 유효성을 검증합니다.
|
||||
5. **상태 관리**: React Query를 통한 서버 상태 관리와 Zustand를 통한 클라이언트 상태 관리를 제공합니다.
|
||||
6. **조건부 렌더링**: 게시판 설정에 따라 UI 요소를 조건부로 렌더링합니다.
|
||||
7. **다양한 게시물 유형 지원**: 일반, 이미지, 동영상, 공지사항 등 다양한 게시물 유형을 지원합니다.
|
||||
|
||||
## 확장 가능성
|
||||
|
||||
1. **다국어 지원**: 게시물 내용의 다국어 지원 기능 추가
|
||||
2. **게시물 버전 관리**: 게시물 수정 이력 관리 기능 추가
|
||||
3. **게시물 예약 발행**: 특정 시간에 자동으로 게시되는 기능 추가
|
||||
4. **게시물 통계**: 조회수, 좋아요 등 게시물 통계 기능 강화
|
||||
5. **게시물 공유**: 소셜 미디어 공유 기능 추가
|
||||
6. **게시물 알림**: 새 게시물 알림 기능 추가
|
||||
7. **게시물 태그**: 게시물 태그 및 카테고리 기능 추가
|
||||
|
||||
## 주의사항
|
||||
|
||||
1. 게시물 삭제 시 연관된 첨부파일, 댓글 등의 처리에 주의가 필요합니다.
|
||||
2. 게시물 이동 시 게시판 유형 간 호환성 문제가 발생할 수 있습니다.
|
||||
3. 첨부파일 업로드 시 파일 크기 및 유형 제한을 준수해야 합니다.
|
||||
4. 게시 기간 설정 시 시간대(timezone) 처리에 주의가 필요합니다.
|
||||
5. 비로그인 사용자의 게시물 작성 시 비밀번호 관리에 주의가 필요합니다.
|
||||
@@ -0,0 +1,96 @@
|
||||
# 프론트엔드 디버깅 환경 구축 및 Windsurf 디버깅 가이드
|
||||
|
||||
이 문서는 본 프로젝트(Next.js 기반 프론트엔드)에서 Windsurf 및 브라우저/VSCode를 활용해 효율적으로 디버깅하는 방법을 안내합니다.
|
||||
|
||||
---
|
||||
|
||||
## 1. 개발 환경 준비
|
||||
|
||||
### 1) Node.js 및 패키지 설치
|
||||
- Node.js 18.x 이상 필요
|
||||
- 루트 디렉토리에서:
|
||||
```bash
|
||||
npm install
|
||||
# 또는
|
||||
pnpm install
|
||||
```
|
||||
|
||||
### 2) 환경 변수 파일 준비
|
||||
- `.env.local` 등 환경 변수 파일을 복사/생성합니다.
|
||||
- API 엔드포인트 등 민감 정보는 환경 변수로 관리하세요.
|
||||
|
||||
### 3) Next.js 개발 서버 실행
|
||||
- 아래 명령어로 개발 서버 실행:
|
||||
```bash
|
||||
npm run dev
|
||||
# 또는
|
||||
pnpm dev
|
||||
```
|
||||
- 기본 포트: `3000` (필요시 `.env` 또는 `package.json`에서 변경)
|
||||
|
||||
---
|
||||
|
||||
## 2. Windsurf 디버깅 환경 구축
|
||||
|
||||
### 1) 소스맵 활성화
|
||||
- Next.js는 개발 모드에서 자동으로 소스맵을 제공합니다.
|
||||
- 별도 설정 없이 브라우저에서 TypeScript/JSX 소스 디버깅 가능.
|
||||
|
||||
### 2) 브라우저 디버깅
|
||||
- Chrome/Edge 등에서 F12 → `Sources` 탭에서 실제 소스 파일에 브레이크포인트 설정.
|
||||
- step-by-step 실행, 변수 값 확인, 콜스택 추적 가능.
|
||||
|
||||
### 3) VSCode 디버깅 (선택)
|
||||
- `.vscode/launch.json` 파일 생성:
|
||||
```json
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Next.js: Chrome 브라우저에서 디버깅",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
- VSCode에서 디버깅 세션 시작 → 브레이크포인트, 변수 추적 가능.
|
||||
|
||||
### 4) Windsurf 연동
|
||||
- Windsurf에서 “로컬 서버 연결” 기능으로 `http://localhost:3000` 접속.
|
||||
- 브라우저 프리뷰, 콘솔 로그, 네트워크 요청, 소스맵 기반 디버깅 가능.
|
||||
- "디버깅 세션 시작" 버튼 클릭 시 브라우저 개발자 도구와 동일하게 동작.
|
||||
|
||||
---
|
||||
|
||||
## 3. 디버깅 팁
|
||||
|
||||
- **핫 리로드**: 코드 수정 시 자동 반영.
|
||||
- **React DevTools**: 컴포넌트 상태, props, hooks 실시간 확인.
|
||||
- **네트워크 탭**: API 요청/응답 실시간 확인.
|
||||
- **에러 오버레이**: Next.js가 런타임/빌드 에러를 화면에 표시.
|
||||
|
||||
---
|
||||
|
||||
## 4. 문제 해결 체크리스트
|
||||
|
||||
- **빌드/런타임 에러**: 터미널과 브라우저 메시지 확인.
|
||||
- **API 통신 문제**: 네트워크 탭에서 요청/응답 확인, 환경 변수 및 서버 상태 점검.
|
||||
- **소스맵 미작동**: `next.config.js` 등에서 소스맵 관련 옵션 확인.
|
||||
|
||||
---
|
||||
|
||||
## 5. Windsurf 디버깅 절차 요약
|
||||
|
||||
1. 의존성 설치 및 개발 서버 실행 (`npm run dev`)
|
||||
2. Windsurf에서 “로컬 서버 연결”로 `http://localhost:3000` 접속
|
||||
3. 브라우저 프리뷰에서 UI/네트워크/콘솔/소스 디버깅
|
||||
4. 필요시 VSCode에서 브레이크포인트 디버깅 병행
|
||||
|
||||
---
|
||||
|
||||
## 참고
|
||||
- Next.js 공식문서: https://nextjs.org/docs
|
||||
- Windsurf 공식문서 및 지원 채널 참고
|
||||
+214
@@ -0,0 +1,214 @@
|
||||
# Project Improvement Plan
|
||||
|
||||
This document outlines a detailed plan for improving the frontend project based on the requirements and goals specified in `requirements.md`.
|
||||
|
||||
## Executive Summary
|
||||
|
||||
After analyzing the project requirements, we've identified several key areas for improvement to enhance the developer experience, code quality, performance, and user experience across all applications. This plan provides a roadmap for implementing these improvements in a phased approach, considering the constraints and future considerations outlined in the requirements.
|
||||
|
||||
## Architecture Improvements
|
||||
|
||||
### Component Library Enhancement
|
||||
|
||||
**Rationale**: The shared component library is central to achieving unified user experience and code reusability goals.
|
||||
|
||||
**Action Items**:
|
||||
1. Audit existing components in `packages/common/components` to identify gaps and inconsistencies
|
||||
2. Implement missing core components based on Shadcn UI patterns
|
||||
3. Enhance component documentation with usage examples and best practices
|
||||
4. Create a component development checklist to ensure all new components meet accessibility standards
|
||||
5. Implement a versioning strategy for components to manage breaking changes
|
||||
|
||||
### Monorepo Structure Optimization
|
||||
|
||||
**Rationale**: Optimizing the monorepo structure will improve build times and developer experience.
|
||||
|
||||
**Action Items**:
|
||||
1. Review and optimize Turborepo cache configuration
|
||||
2. Implement project references in TypeScript configuration for better type checking
|
||||
3. Create clear guidelines for code organization within each application
|
||||
4. Establish conventions for sharing code between applications
|
||||
5. Implement better dependency management to prevent version conflicts
|
||||
|
||||
### State Management Standardization
|
||||
|
||||
**Rationale**: Consistent state management patterns will improve maintainability and reduce bugs.
|
||||
|
||||
**Action Items**:
|
||||
1. Create standardized Zustand store patterns for common use cases
|
||||
2. Implement store persistence where appropriate
|
||||
3. Develop utilities for store debugging and testing
|
||||
4. Document best practices for state management
|
||||
5. Create examples of complex state management scenarios
|
||||
|
||||
## Performance Optimization
|
||||
|
||||
### Bundle Size Reduction
|
||||
|
||||
**Rationale**: Meeting the 500KB compressed bundle size constraint requires careful optimization.
|
||||
|
||||
**Action Items**:
|
||||
1. Implement bundle analysis in the CI pipeline
|
||||
2. Set up code splitting for all applications
|
||||
3. Optimize image loading and processing
|
||||
4. Implement tree-shaking for all dependencies
|
||||
5. Create guidelines for importing third-party libraries
|
||||
|
||||
### Runtime Performance
|
||||
|
||||
**Rationale**: Achieving the FCP and TTI targets requires optimizing the critical rendering path.
|
||||
|
||||
**Action Items**:
|
||||
1. Implement performance monitoring in production
|
||||
2. Optimize component rendering with React.memo and useMemo
|
||||
3. Implement lazy loading for non-critical components
|
||||
4. Optimize CSS delivery and rendering
|
||||
5. Create performance testing scenarios for CI
|
||||
|
||||
## Developer Experience
|
||||
|
||||
### Documentation Improvements
|
||||
|
||||
**Rationale**: Better documentation will help teams with varying levels of expertise work effectively.
|
||||
|
||||
**Action Items**:
|
||||
1. Create a comprehensive developer onboarding guide
|
||||
2. Document common patterns and solutions
|
||||
3. Implement automated documentation generation for components
|
||||
4. Create interactive examples for complex components
|
||||
5. Establish a process for keeping documentation up-to-date
|
||||
|
||||
### Tooling Enhancements
|
||||
|
||||
**Rationale**: Better tooling will improve developer productivity and code quality.
|
||||
|
||||
**Action Items**:
|
||||
1. Enhance ESLint and Prettier configurations for stricter code quality checks
|
||||
2. Implement pre-commit hooks for code formatting and linting
|
||||
3. Create custom VS Code extensions for project-specific tasks
|
||||
4. Implement automated dependency updates with Renovate
|
||||
5. Create custom CLI tools for common development tasks
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Component Testing
|
||||
|
||||
**Rationale**: Comprehensive testing is essential for maintaining quality as the codebase grows.
|
||||
|
||||
**Action Items**:
|
||||
1. Establish minimum test coverage requirements for components
|
||||
2. Implement visual regression testing with Storybook
|
||||
3. Create testing utilities for common testing scenarios
|
||||
4. Implement accessibility testing in the CI pipeline
|
||||
5. Document testing best practices and patterns
|
||||
|
||||
### Integration Testing
|
||||
|
||||
**Rationale**: Integration testing ensures that components work together as expected.
|
||||
|
||||
**Action Items**:
|
||||
1. Implement end-to-end testing with Cypress
|
||||
2. Create testing environments for each application
|
||||
3. Implement API mocking for testing
|
||||
4. Create test data generators for common scenarios
|
||||
5. Implement performance testing in the CI pipeline
|
||||
|
||||
## Accessibility Improvements
|
||||
|
||||
### Accessibility Standards
|
||||
|
||||
**Rationale**: Meeting accessibility requirements is a core goal of the project.
|
||||
|
||||
**Action Items**:
|
||||
1. Conduct an accessibility audit of existing components
|
||||
2. Implement ARIA attributes and keyboard navigation for all components
|
||||
3. Create accessibility testing guidelines
|
||||
4. Implement automated accessibility testing in the CI pipeline
|
||||
5. Provide training on accessibility best practices
|
||||
|
||||
### Responsive Design
|
||||
|
||||
**Rationale**: Ensuring responsive design is essential for a good user experience on all devices.
|
||||
|
||||
**Action Items**:
|
||||
1. Establish responsive design guidelines
|
||||
2. Create responsive design testing scenarios
|
||||
3. Implement responsive design utilities
|
||||
4. Optimize for mobile devices
|
||||
5. Create responsive design documentation
|
||||
|
||||
## Technical Debt Reduction
|
||||
|
||||
### Legacy Code Migration
|
||||
|
||||
**Rationale**: Gradually migrating legacy code will improve maintainability and reduce technical debt.
|
||||
|
||||
**Action Items**:
|
||||
1. Identify and prioritize legacy code for migration
|
||||
2. Create a migration plan for each legacy component
|
||||
3. Implement automated tests for legacy code
|
||||
4. Create migration guidelines and best practices
|
||||
5. Establish metrics for tracking migration progress
|
||||
|
||||
### Code Quality Improvements
|
||||
|
||||
**Rationale**: Improving code quality will reduce bugs and improve maintainability.
|
||||
|
||||
**Action Items**:
|
||||
1. Implement stricter TypeScript configurations
|
||||
2. Create code quality metrics and dashboards
|
||||
3. Implement code reviews for all pull requests
|
||||
4. Create code quality guidelines
|
||||
5. Implement automated code quality checks in the CI pipeline
|
||||
|
||||
## Future-Proofing
|
||||
|
||||
### Internationalization Preparation
|
||||
|
||||
**Rationale**: Preparing for internationalization now will make it easier to implement in the future.
|
||||
|
||||
**Action Items**:
|
||||
1. Research and select an internationalization library
|
||||
2. Create guidelines for internationalization-friendly component design
|
||||
3. Implement a proof-of-concept for internationalization
|
||||
4. Document internationalization best practices
|
||||
5. Create a plan for implementing internationalization across all applications
|
||||
|
||||
### Theming Support
|
||||
|
||||
**Rationale**: Implementing theming support will enable white-labeling in the future.
|
||||
|
||||
**Action Items**:
|
||||
1. Research and select a theming approach
|
||||
2. Implement a proof-of-concept for theming
|
||||
3. Create theming guidelines for component development
|
||||
4. Document theming best practices
|
||||
5. Create a plan for implementing theming across all applications
|
||||
|
||||
## Implementation Timeline
|
||||
|
||||
### Phase 1: Foundation (Months 1-2)
|
||||
|
||||
Focus on architecture improvements, developer experience, and establishing standards.
|
||||
|
||||
### Phase 2: Quality and Performance (Months 3-4)
|
||||
|
||||
Focus on testing, performance optimization, and accessibility improvements.
|
||||
|
||||
### Phase 3: Technical Debt and Future-Proofing (Months 5-6)
|
||||
|
||||
Focus on technical debt reduction and preparing for future requirements.
|
||||
|
||||
## Success Metrics
|
||||
|
||||
To measure the success of this improvement plan, we will track the following metrics:
|
||||
|
||||
1. **Build Performance**: CI/CD pipeline completion time, local build time
|
||||
2. **Runtime Performance**: FCP, TTI, bundle size
|
||||
3. **Code Quality**: Test coverage, TypeScript strict mode compliance
|
||||
4. **Developer Experience**: Time to onboard new developers, developer satisfaction surveys
|
||||
5. **User Experience**: Accessibility compliance, user satisfaction surveys
|
||||
|
||||
## Conclusion
|
||||
|
||||
This improvement plan provides a comprehensive roadmap for enhancing the frontend project in line with the requirements and goals specified in `requirements.md`. By following this plan, we can create a more maintainable, performant, and user-friendly frontend while enabling team autonomy and code reusability.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Project Requirements
|
||||
|
||||
This document outlines the key requirements, goals, and constraints for the frontend project.
|
||||
|
||||
## Core Goals
|
||||
|
||||
1. **Unified User Experience**: Create a consistent user experience across all applications within the monorepo.
|
||||
2. **Code Reusability**: Maximize code reuse through shared components and utilities.
|
||||
3. **Team Autonomy**: Allow different teams to work independently on their applications while maintaining consistency.
|
||||
4. **Performance Optimization**: Ensure fast loading times and smooth user interactions across all applications.
|
||||
5. **Accessibility**: Make all applications accessible to users with disabilities.
|
||||
6. **Maintainability**: Ensure the codebase is easy to maintain and extend over time.
|
||||
|
||||
## Technical Requirements
|
||||
|
||||
### Architecture
|
||||
|
||||
1. Maintain the monorepo structure using pnpm workspaces and Turborepo.
|
||||
2. Follow the established project structure:
|
||||
- `apps/`: Contains the applications
|
||||
- `common/`: Common features provided by the main company
|
||||
- `cs/`, `wr/`, `as/`: Applications developed by different teams
|
||||
- `packages/`: Contains shared packages
|
||||
- `common/`: Common packages provided by the main company
|
||||
- `assets/`: Shared assets (styles, images, etc.)
|
||||
- `components/`: Shared UI components
|
||||
- `business_components/`: Business-specific components
|
||||
- `config/`: Shared configuration
|
||||
- `network/`: Network-related utilities
|
||||
|
||||
### Development Standards
|
||||
|
||||
1. Use TypeScript for all code to ensure type safety.
|
||||
2. Follow ESLint and Prettier configurations for consistent code style.
|
||||
3. Implement comprehensive testing using Storybook for component development and testing.
|
||||
4. Use Zustand for state management across applications.
|
||||
5. Implement TanStack Query (React Query) for data fetching and caching.
|
||||
6. Use Axios for HTTP requests.
|
||||
|
||||
### UI/UX Requirements
|
||||
|
||||
1. Use Tailwind CSS for styling with consistent design patterns.
|
||||
2. Implement Radix UI for accessible UI primitives.
|
||||
3. Follow Shadcn UI patterns for component design.
|
||||
4. Use TanStack Table for data tables and Chart.js for charts.
|
||||
5. Ensure responsive design for all screen sizes.
|
||||
6. Maintain a consistent color scheme and typography across all applications.
|
||||
|
||||
## Constraints
|
||||
|
||||
1. **Performance Budgets**:
|
||||
- First Contentful Paint (FCP): < 1.8s
|
||||
- Time to Interactive (TTI): < 3.5s
|
||||
- Total Bundle Size: < 500KB (compressed)
|
||||
|
||||
2. **Browser Support**:
|
||||
- Support the last two versions of major browsers (Chrome, Firefox, Safari, Edge)
|
||||
- No support required for Internet Explorer
|
||||
|
||||
3. **Deployment Constraints**:
|
||||
- CI/CD pipeline must complete within 10 minutes
|
||||
- Zero-downtime deployments required
|
||||
|
||||
4. **Team Constraints**:
|
||||
- Multiple teams working on different applications
|
||||
- Need for clear boundaries and interfaces between teams
|
||||
- Varying levels of expertise with the technology stack
|
||||
|
||||
5. **Technical Debt**:
|
||||
- Some legacy code exists in certain applications
|
||||
- Need to gradually migrate to the new architecture
|
||||
|
||||
## Future Considerations
|
||||
|
||||
1. **Internationalization**: Prepare for multi-language support in the future.
|
||||
2. **Theming**: Consider implementing a theming system for white-labeling.
|
||||
3. **Mobile Applications**: Prepare for potential React Native integration.
|
||||
4. **Micro-frontends**: Evaluate the possibility of moving to a micro-frontend architecture.
|
||||
Reference in New Issue
Block a user