Skip to content

Commit 45a96ec

Browse files
committed
feat: 경매장 거래 내역 데스크탑 화면 및 상단네비게이션 메뉴 변경
1 parent 71efb58 commit 45a96ec

10 files changed

Lines changed: 788 additions & 47 deletions

File tree

README.md

Lines changed: 140 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,29 +30,80 @@
3030
```
3131
devnogi-front/
3232
├── src/
33-
│ ├── app/ # Next.js App Router
34-
│ │ ├── (auth)/ # 인증 관련 페이지 (sign-in)
35-
│ │ ├── (main)/ # 메인 애플리케이션 (auction, auction-history, community)
36-
│ │ ├── api/ # BFF API 라우트 (게이트웨이로 프록시)
37-
│ │ └── fonts/ # 폰트 파일 (Pretendard, Mabinogi Classic)
33+
│ ├── app/ # Next.js App Router
34+
│ │ ├── (auth)/ # 인증 페이지 그룹
35+
│ │ │ ├── sign-in/ # 로그인 페이지
36+
│ │ │ ├── sign-up/ # 회원가입 페이지
37+
│ │ │ └── social-callback/ # 소셜 로그인 콜백
38+
│ │ ├── (main)/ # 메인 애플리케이션 (NavBar 포함)
39+
│ │ │ ├── auction/ # 경매장 페이지
40+
│ │ │ ├── auction-history/ # 거래 내역 페이지
41+
│ │ │ ├── community/ # 커뮤니티 페이지
42+
│ │ │ │ ├── [id]/ # 상세 페이지
43+
│ │ │ │ └── write/ # 글쓰기 페이지
44+
│ │ │ └── mypage/ # 마이페이지
45+
│ │ ├── api/ # BFF API 라우트
46+
│ │ │ ├── auth/ # 인증 API
47+
│ │ │ ├── posts/ # 게시글 API
48+
│ │ │ ├── comments/ # 댓글 API
49+
│ │ │ ├── boards/ # 게시판 API
50+
│ │ │ ├── auction-history/ # 경매 내역 API
51+
│ │ │ └── item-categories/ # 아이템 카테고리 API
52+
│ │ └── fonts/ # 폰트 파일
3853
│ ├── components/
39-
│ │ ├── page/ # 페이지별 컴포넌트
40-
│ │ │ ├── auction/ # 경매장 관련
41-
│ │ │ ├── auction-history/ # 거래 내역 관련
42-
│ │ │ └── community/ # 커뮤니티 관련
43-
│ │ └── ui/ # Shadcn UI 컴포넌트
54+
│ │ ├── page/ # 페이지별 컴포넌트
55+
│ │ │ ├── auction/ # 경매장 관련
56+
│ │ │ ├── auction-history/ # 거래 내역 관련
57+
│ │ │ ├── community/ # 커뮤니티 관련
58+
│ │ │ └── auth/ # 인증 관련
59+
│ │ ├── commons/ # 공용 컴포넌트
60+
│ │ ├── navigation/ # 네비게이션 컴포넌트
61+
│ │ ├── errors/ # 에러 페이지 컴포넌트
62+
│ │ └── ui/ # Shadcn UI 컴포넌트
63+
│ ├── hooks/ # 커스텀 React Hooks
64+
│ ├── types/ # TypeScript 타입 정의
65+
│ ├── contexts/ # React Context
4466
│ ├── lib/
45-
│ │ └── api/ # API 클라이언트 & 서버 설정
46-
│ │ ├── clients.ts # clientAxios, TanStack Query 설정
47-
│ │ └── server.ts # createServerAxios, MSW 초기화
48-
│ ├── mocks/ # MSW 모킹 설정
49-
│ │ ├── data/ # Mock JSON 데이터
50-
│ │ ├── server.ts # MSW 핸들러
51-
│ │ └── initServer.ts # MSW 서버 초기화
52-
│ └── utils/ # 공통 유틸리티 함수
53-
└── public/ # 정적 파일
67+
│ │ ├── api/ # API 클라이언트 & 서버 설정
68+
│ │ │ ├── clients.ts # clientAxios, TanStack Query 설정
69+
│ │ │ ├── server.ts # createServerAxios, MSW 초기화
70+
│ │ │ └── gateway-selector.ts # 게이트웨이 URL 선택
71+
│ │ └── auth/ # 인증 관련 유틸
72+
│ ├── mocks/ # MSW 모킹 설정
73+
│ │ ├── data/ # Mock JSON 데이터
74+
│ │ ├── server.ts # MSW 핸들러
75+
│ │ └── initServer.ts # MSW 서버 초기화
76+
│ └── utils/ # 공통 유틸리티 함수
77+
├── public/ # 정적 파일
78+
├── Dockerfile # Docker 멀티 스테이지 빌드
79+
└── docker-compose-*.yml # Docker Compose 설정
5480
```
5581

82+
## 주요 기능
83+
84+
### 인증 시스템
85+
- **이메일 로그인**: 이메일/비밀번호 기반 로그인
86+
- **소셜 로그인**: 구글, 카카오, 네이버 OAuth 연동
87+
- **회원가입**: 이메일/닉네임 중복 체크, 약관 동의
88+
- **세션 관리**: 쿠키 기반 인증 상태 유지
89+
90+
### 커뮤니티
91+
- **게시글 목록**: 카드 형태 UI, 무한 스크롤
92+
- **카테고리 필터**: 게시판별 분류
93+
- **정렬 옵션**: 최신순, 인기순, 댓글순, 조회순
94+
- **게시글 상세**: 댓글 작성 및 조회
95+
- **글쓰기**: 모달 기반 게시글 작성
96+
97+
### 경매 내역
98+
- **무한 스크롤**: TanStack Query 기반 페이지네이션
99+
- **검색 기능**: 아이템 이름 검색
100+
- **필터링**: 카테고리, 가격 범위, 날짜 범위, 아이템 옵션
101+
- **정렬**: 거래 최신순, 가격순 등
102+
- **반응형 UI**: 데스크톱 사이드바 / 모바일 모달 필터
103+
104+
### 마이페이지
105+
- 사용자 정보 조회 및 프로필 편집
106+
56107
## 아키텍처
57108

58109
### BFF (Backend for Frontend) 패턴
@@ -94,6 +145,18 @@ Backend Services
94145
- `staleTime: 200ms`
95146
- 윈도우 포커스, 재연결, 마운트 시 자동 refetch 비활성화
96147

148+
### 커스텀 Hooks
149+
150+
| Hook | 용도 |
151+
|------|------|
152+
| `useInfiniteAuctionHistory` | 경매 내역 무한 스크롤 |
153+
| `useAuctionHistory` | 경매 내역 단일 페이지 조회 |
154+
| `useInfinitePosts` | 게시글 무한 스크롤 |
155+
| `usePostDetail` | 게시글 상세 조회 |
156+
| `useItemCategories` | 아이템 카테고리 트리 조회 |
157+
| `useSearchOptions` | 검색 옵션 조회 |
158+
| `useItemInfos` | 아이템 정보 조회 |
159+
97160
### API 모킹 (MSW)
98161

99162
**활성화 조건:**
@@ -122,6 +185,7 @@ http://168.107.43.221:8080/
122185
|--------|--------|---------|-----------|
123186
| OPEN API BATCH | `/oab` | [Swagger UI](http://138.2.126.248:8080/swagger-ui/index.html) | 경매장, 거래 내역 |
124187
| 커뮤니티 서버 | `/dcs/api` | [Swagger UI](http://3.39.119.27/swagger-ui/index.html) | 게시판, 게시글, 댓글 |
188+
| Auth 서버 | `/das/api` | - | 인증, 회원가입, 소셜 로그인 |
125189

126190
**커뮤니티 API 응답 구조:**
127191
```typescript
@@ -134,6 +198,38 @@ http://168.107.43.221:8080/
134198
}
135199
```
136200

201+
### API 라우트 목록
202+
203+
**인증 API (`/api/auth/*`)**
204+
```
205+
POST /api/auth/login # 로그인
206+
POST /api/auth/signup # 회원가입
207+
POST /api/auth/signup/social # 소셜 회원가입
208+
POST /api/auth/logout # 로그아웃
209+
GET /api/auth/me # 현재 사용자 정보
210+
POST /api/auth/check-email # 이메일 중복 체크
211+
POST /api/auth/check-nickname # 닉네임 중복 체크
212+
```
213+
214+
**커뮤니티 API (`/api/posts/*`, `/api/comments/*`)**
215+
```
216+
GET /api/posts # 전체 게시글 조회
217+
GET /api/posts/[id] # 게시판별 게시글 조회
218+
GET /api/posts/details/[id] # 게시글 상세 조회
219+
GET /api/comments/[postId] # 댓글 조회
220+
POST /api/comments/[postId] # 댓글 작성
221+
GET /api/boards # 게시판 목록 조회
222+
```
223+
224+
**경매 API (`/api/auction-history/*`)**
225+
```
226+
GET /api/auction-history # 경매 내역 조회
227+
POST /api/auction-history/search # 상세 검색
228+
GET /api/item-categories # 아이템 카테고리 조회
229+
GET /api/item-infos # 아이템 정보 조회
230+
GET /api/search-option # 검색 옵션 조회
231+
```
232+
137233
### Slash Commands
138234

139235
프로젝트에서 사용 가능한 커스텀 명령어:
@@ -315,6 +411,31 @@ npm run lint
315411
npm test
316412
```
317413

414+
## Docker 배포
415+
416+
### 로컬 개발
417+
418+
```bash
419+
# 개발 환경 실행
420+
docker-compose -f docker-compose-dev.yml up
421+
422+
# 로컬 환경 실행
423+
docker-compose -f docker-compose-local.yml up
424+
```
425+
426+
### 프로덕션 배포
427+
428+
```bash
429+
# 프로덕션 빌드 및 실행
430+
docker-compose -f docker-compose-prod.yaml up -d
431+
```
432+
433+
**Docker 설정:**
434+
- 멀티 스테이지 빌드 (node:20-alpine)
435+
- Next.js standalone 출력 모드
436+
- 기본 포트: 3010
437+
- 보안 사용자(nextjs)로 실행
438+
318439
## 디자인 시스템
319440

320441
### 디자인 철학

src/app/(main)/auction-history/page.tsx

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import MobileFilterModal from "@/components/page/auction-history/MobileFilterMod
1010
import { useItemCategories } from "@/hooks/useItemCategories";
1111
import { ItemCategory } from "@/data/item-category";
1212
import { useInfiniteAuctionHistory } from "@/hooks/useInfiniteAuctionHistory";
13+
import { useAuctionHistoryLayout } from "@/hooks/useAuctionHistoryLayout";
1314
import { AuctionHistorySearchParams } from "@/types/auction-history";
1415
import { ActiveFilter } from "@/types/search-filter";
1516
import { useState, useEffect, useMemo, useRef, useCallback, Suspense } from "react";
@@ -72,6 +73,14 @@ export default function Page() {
7273
const { data: categories = [], isLoading: isCategoriesLoading } =
7374
useItemCategories();
7475

76+
// 레이아웃 모드 관리 - 화면 크기에 따라 자동 전환
77+
const {
78+
layoutMode,
79+
showCategorySidebar,
80+
showFilterSidebar,
81+
showMobileFilter,
82+
} = useAuctionHistoryLayout();
83+
7584
const {
7685
data,
7786
isLoading,
@@ -377,16 +386,24 @@ export default function Page() {
377386
<UrlParamsReader onParamsLoad={handleUrlParamsLoad} />
378387
</Suspense>
379388

380-
{/* Fixed Floating Category Sidebar - Only visible on 2xl+ screens (1536px+) */}
381-
<div className="fixed left-4 top-[158px] bottom-8 w-56 z-40 hidden 2xl:block">
382-
<CategorySection
383-
selectedId={selectedCategory}
384-
onSelect={handleCategorySelect}
385-
expandedIds={expandedIds}
386-
onToggleExpand={handleToggleExpand}
387-
categories={categories}
388-
/>
389-
</div>
389+
{/* Fixed Floating Category Sidebar - 메인 콘텐츠 기준 왼쪽에 배치
390+
위치 계산: 중앙(50%) - 메인콘텐츠반(448px) - 간격(6px) - 카테고리너비(224px) = 50% - 678px
391+
최소값 보장: max(16px, calc(50% - 678px))
392+
상단 위치: 검색 필터 컴포넌트와 동일한 140px로 정렬 */}
393+
{showCategorySidebar && (
394+
<div
395+
className="fixed top-[140px] bottom-8 w-56 z-40"
396+
style={{ left: "max(16px, calc(50% - 678px))" }}
397+
>
398+
<CategorySection
399+
selectedId={selectedCategory}
400+
onSelect={handleCategorySelect}
401+
expandedIds={expandedIds}
402+
onToggleExpand={handleToggleExpand}
403+
categories={categories}
404+
/>
405+
</div>
406+
)}
390407

391408
{/* Category Modal - Visible on lg and below */}
392409
<CategoryModal
@@ -399,11 +416,24 @@ export default function Page() {
399416
categories={categories}
400417
/>
401418

402-
{/* Centered Main Content Container */}
403-
<div className="min-h-full flex justify-center [scrollbar-gutter:stable]">
419+
{/* Centered Main Content Container
420+
레이아웃 모드에 따라 좌우 패딩 조정:
421+
- desktop: 양쪽 사이드바 공간 확보 (px-72 = 288px)
422+
- tablet: 오른쪽 필터 공간만 확보 (pr-72 = 288px)
423+
- mobile: 패딩 없음 */}
424+
<div
425+
className={`min-h-full flex justify-center [scrollbar-gutter:stable] ${
426+
showCategorySidebar
427+
? "px-72"
428+
: showFilterSidebar
429+
? "pr-72"
430+
: ""
431+
}`}
432+
>
404433
<div className="w-full max-w-4xl px-4 md:px-6 pt-4 md:pt-6 pb-4 md:pb-8">
405-
{/* Mobile Filter Chips - Only visible on lg and below */}
406-
<div className="mb-4 lg:hidden">
434+
{/* Mobile Filter Chips - 모바일 뷰에서만 표시 */}
435+
{showMobileFilter && (
436+
<div className="mb-4">
407437
<MobileFilterChips
408438
activeFilters={{
409439
hasCategory: selectedCategory !== "all",
@@ -416,10 +446,12 @@ export default function Page() {
416446
onDateClick={() => setMobileFilterType("date")}
417447
onOptionsClick={() => setMobileFilterType("options")}
418448
/>
419-
</div>
449+
</div>
450+
)}
420451

421-
{/* Search Section - Only visible on lg+ screens */}
422-
<div className="mb-6 hidden lg:block">
452+
{/* Search Section - 필터 사이드바가 표시될 때만 보임 (tablet/desktop) */}
453+
{showFilterSidebar && (
454+
<div className="mb-6">
423455
<SearchSection
424456
path={categoryPath}
425457
onCategorySelect={handleCategorySelect}
@@ -428,7 +460,8 @@ export default function Page() {
428460
onSearch={handleSearch}
429461
onCategoryMenuClick={() => setIsCategoryModalOpen(true)}
430462
/>
431-
</div>
463+
</div>
464+
)}
432465

433466
{/* Results Section */}
434467
<div>
@@ -502,17 +535,18 @@ export default function Page() {
502535
</div>
503536
</div>
504537

505-
{/* Fixed Floating Filter Card - Right Side - Only visible on lg+ screens */}
506-
<div className="hidden lg:block">
538+
{/* Fixed Floating Filter Card - 메인 콘텐츠 기준 오른쪽에 배치 */}
539+
{showFilterSidebar && (
507540
<SearchFilterCard
508541
onFilterApply={handleFilterApply}
509542
isModal={isFilterModalOpen}
510543
onClose={() => setIsFilterModalOpen(false)}
544+
layoutMode={layoutMode}
511545
/>
512-
</div>
546+
)}
513547

514-
{/* Mobile Filter Modal - Only visible on lg and below when chip is clicked */}
515-
<div className="lg:hidden">
548+
{/* Mobile Filter Modal - 모바일 뷰에서만 표시 */}
549+
{showMobileFilter && (
516550
<MobileFilterModal
517551
isOpen={mobileFilterType !== null}
518552
onClose={() => setMobileFilterType(null)}
@@ -528,7 +562,7 @@ export default function Page() {
528562
categories={categories}
529563
onApply={handleMobileFilterApply}
530564
/>
531-
</div>
565+
)}
532566

533567
</div>
534568
);

src/app/(main)/layout.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import ThreeTierNav from "@/components/navigation/ThreeTierNav";
2+
import Footer from "@/components/Footer";
23
import { Toaster } from "sonner";
34

45
export default function RootLayout({
@@ -20,6 +21,9 @@ export default function RootLayout({
2021
</div>
2122
</main>
2223

24+
{/* Footer */}
25+
<Footer />
26+
2327
{/* Toast notifications */}
2428
<Toaster position="top-center" richColors />
2529
</div>

src/app/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import "./globals.css";
33
import Providers from "@/app/_providers/Providers";
44

55
export const metadata: Metadata = {
6-
title: "Devnogi",
6+
title: "메모노기 - 마비노기 통계 및 커뮤니티",
77
description: "마비노기 정보 커뮤니티",
88
};
99

0 commit comments

Comments
 (0)