Skip to content

Commit dd44e1d

Browse files
committed
Merge remote-tracking branch 'origin/dev' into refactor/recipeFetch
2 parents 06f78da + 28b5eb0 commit dd44e1d

File tree

19 files changed

+110
-75
lines changed

19 files changed

+110
-75
lines changed

README.md

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# SSOUL 프로젝트 인수인계 문서
22

3-
## �� 프로젝트 개요
3+
## 프로젝트 개요
44

55
**프로젝트명**: SSOUL (칵테일을 좋아하는 사람들을 위한 서비스)
66
**기술 스택**: Next.js 15, React 19, TypeScript, Tailwind CSS
77
**저장소**: https://github.com/prgrms-web-devcourse-final-project/WEB5_6_HaeDokCoding_FE
88

9-
## �� 시작하기
9+
## 시작하기
1010

1111
### 개발 환경 설정
1212
```bash
@@ -31,7 +31,7 @@ npm run lint
3131
- `NEXT_PUBLIC_API_URL_DEV`: 개발 환경 API URL
3232
- `NEXT_PUBLIC_API_URL_PROD`: 운영 환경 API URL
3333

34-
## �� 프로젝트 구조
34+
## 프로젝트 구조
3535

3636
```
3737
src/
@@ -60,7 +60,7 @@ src/
6060
└── utills/ # 유틸리티 함수
6161
```
6262

63-
## �� 주요 기능
63+
## 주요 기능
6464

6565
### 1. 인증 시스템
6666
- **소셜 로그인**: Google, Kakao, Naver 지원
@@ -72,19 +72,19 @@ src/
7272

7373
### 2. 페이지별 기능
7474

75-
#### �� 메인 페이지 (`/`)
75+
#### 메인 페이지 (`/`)
7676
- 현재 기본 구조만 구현됨
7777
- 추후 확장 예정
7878

79-
#### �� 칵테일 레시피 (`/recipe`)
79+
#### 칵테일 레시피 (`/recipe`)
8080
- **주요 컴포넌트**:
8181
- `CocktailList`: 칵테일 목록 표시
8282
- `Accordion`: 필터링 옵션
8383
- `SelectBox`: 정렬 옵션
8484
- **기능**: 검색, 필터링, 정렬
8585
- **상세 페이지**: `/recipe/[id]` - 개별 칵테일 상세 정보
8686

87-
#### �� 커뮤니티 (`/community`)
87+
#### 커뮤니티 (`/community`)
8888
- **주요 컴포넌트**:
8989
- `PostCard`: 게시물 카드
9090
- `CommunityTab`: 카테고리 탭
@@ -93,28 +93,28 @@ src/
9393
- **글쓰기**: `/community/write`
9494
- **상세 페이지**: `/community/[id]`
9595

96-
#### �� 취향 추천 (`/recommend`)
96+
#### 취향 추천 (`/recommend`)
9797
- **챗봇 기반 추천**: `ChatSection` 컴포넌트
9898
- **주요 컴포넌트**:
9999
- `BotMessage`, `UserMessage`: 메시지 컴포넌트
100100
- `BotOptions`: 선택 옵션
101101
- `MessageInput`: 입력창
102102
- `TypingIndicator`: 타이핑 효과
103103

104-
#### �� 마이페이지 (`/mypage`)
104+
#### 마이페이지 (`/mypage`)
105105
- **기본 리다이렉트**: `/mypage``/mypage/mybar`
106106
- **주요 섹션**:
107107
- `/mypage/mybar`: 나만의 바
108108
- `/mypage/my-active`: 활동 내역 (게시물, 댓글, 좋아요)
109109
- `/mypage/my-alarm`: 알림 설정
110110
- `/mypage/my-setting`: 계정 설정
111111

112-
#### �� 로그인 (`/login`)
112+
#### 로그인 (`/login`)
113113
- **소셜 로그인**: `SocialLogin` 컴포넌트
114114
- **성공 페이지**: `/login/success`
115115
- **신규 사용자**: `/login/user/first-user`
116116

117-
## �� 기술적 세부사항
117+
## 기술적 세부사항
118118

119119
### 상태 관리
120120
- **Zustand**: 클라이언트 상태 관리
@@ -136,34 +136,34 @@ src/
136136
- **Husky**: Git hooks
137137
- **Lint-staged**: 커밋 전 검사
138138

139-
## �� 주요 설정 파일
139+
## 주요 설정 파일
140140

141141
- `next.config.ts`: Next.js 설정
142142
- `tailwind.config.js`: Tailwind CSS 설정
143143
- `eslint.config.mjs`: ESLint 설정
144144
- `tsconfig.json`: TypeScript 설정
145145

146-
## �� 반응형 디자인
146+
## 반응형 디자인
147147

148148
프로젝트는 모바일 우선(Mobile-first) 접근 방식을 사용합니다:
149149
- **모바일**: 기본 스타일
150150
- **태블릿**: `md:` prefix
151151
- **데스크톱**: `lg:`, `xl:` prefix
152152

153-
## �� 주의사항
153+
## 주의사항
154154

155155
1. **환경 변수**: 개발/운영 환경에 맞는 API URL 설정 필요
156156
2. **인증 토큰**: localStorage에 저장되므로 보안 고려 필요
157157
3. **API 통신**: `credentials: 'include'` 설정으로 쿠키 기반 인증
158158
4. **Git Hooks**: Husky 설정으로 커밋 전 자동 검사
159159

160-
## �� 추가 문의
160+
## 추가 문의
161161

162162
- **저장소 이슈**: https://github.com/prgrms-web-devcourse-final-project/WEB5_6_HaeDokCoding_FE/issues
163163
- **주요 브랜치**: `main` (메인), `dev` (개발)
164164

165165
---
166166

167167
**작성일**: 2025-10-14
168-
**작성자**: 이성헌
169-
**버전**: 1.0
168+
**작성자**: 정은빈 | 김아현 | 문태민
169+
**버전**: 1.0

next.config.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@ const nextConfig: NextConfig = {
55
scrollRestoration: false,
66
},
77
images: {
8-
domains: ['team2-app-s3-bucket.s3.ap-northeast-2.amazonaws.com'],
8+
// 외부 이미지 최적화 완전 비활성화 (Vercel 유료 기능 회피)
9+
unoptimized: true,
10+
domains: [
11+
'team2-app-s3-bucket.s3.ap-northeast-2.amazonaws.com',
12+
'team2-app-s3-bucket.s3.amazonaws.com',
13+
],
914
remotePatterns: [
1015
{
1116
protocol: 'https',
1217
hostname: 'www.thecocktaildb.com',
1318
},
1419
],
15-
qualities: [25, 50, 75, 90, 100],
1620
},
1721
env: {
1822
NPUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,

src/app/(no-layout)/layout.tsx

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/domains/community/api/fetchPost.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@ export const fetchPostByTab = async ({
5050
const params = new URLSearchParams();
5151

5252
if (category && category !== 'all') {
53-
const categoryId = tabItem.findIndex((tab) => tab.key === category);
54-
if (categoryId >= 0) {
53+
const categoryId = tabItem.findIndex((tab) => tab.key === category) + 1;
54+
if (categoryId > 0) {
5555
params.set('categoryId', categoryId.toString());
5656
}
5757
}

src/domains/community/components/post-info/PostInfo.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function PostInfo({
1717
}: Props) {
1818
return (
1919
<ul
20-
className="flex font-light sm:gap-3 gap-1 sm:text-sm text-xs text-gray"
20+
className="flex font-light sm:gap-3 gap-1 sm:text-sm text-[10px] text-gray"
2121
aria-label="게시글 정보"
2222
>
2323
{hasUserName && (

src/domains/community/detail/ImageSlide.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ function ImageSlide({ imageUrls }: { imageUrls: string[] }) {
3737
quality={90}
3838
priority
3939
onLoad={() => handleImageLoad(img)}
40-
onError={() => handleImageLoad(img)}
40+
onError={(e) => {
41+
// 402 에러 등으로 이미지 로딩 실패 시 fallback 이미지 사용
42+
e.currentTarget.src = '/CocktailDrop.webp';
43+
handleImageLoad(img);
44+
}}
4145
className={`object-contain w-full max-h-[400px] transition-opacity duration-300 ${
4246
loadedImages.has(img) ? 'opacity-100' : 'opacity-0'
4347
}`}

src/domains/community/main/PostCard.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ function PostCard({ posts, isLoading, isEnd, onLoadMore }: Props) {
106106
width={105}
107107
height={105}
108108
className="w-full h-full object-cover self-center rounded-md"
109+
onError={(e) => {
110+
// 402 에러 등으로 이미지 로딩 실패 시 fallback 이미지 사용
111+
e.currentTarget.src = '/CocktailDrop.webp';
112+
}}
109113
/>
110114
)}
111115
</figure>

src/domains/community/write/CompleteBtn.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,28 @@ import Button from '@/shared/components/button/Button';
33
type Props = {
44
mode: 'edit' | 'create';
55
setEditDone: (value: boolean) => void;
6+
isLoading?: boolean;
67
};
78

8-
function CompleteBtn({ mode, setEditDone }: Props) {
9+
function CompleteBtn({ mode, setEditDone, isLoading = false }: Props) {
910
return (
1011
<div className="w-full flex items-center justify-end mt-10">
1112
<Button
1213
type={mode === 'create' ? 'submit' : 'button'}
1314
size="default"
1415
color="default"
16+
disabled={isLoading}
1517
onClick={async () => {
1618
setEditDone(true);
1719
}}
1820
>
19-
{mode === 'create' ? '올리기' : '수정하기'}
21+
{isLoading
22+
? mode === 'create'
23+
? '올리는 중...'
24+
: '수정 중...'
25+
: mode === 'create'
26+
? '올리기'
27+
: '수정하기'}
2028
</Button>
2129
</div>
2230
);

src/domains/community/write/WriteSection.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ function WriteSection({ mode, postId }: Props) {
126126
return;
127127
}
128128

129-
const categoryId = tabItem.findIndex((tab) => tab.label === formData.categoryName);
129+
const categoryId = tabItem.findIndex((tab) => tab.label === formData.categoryName) + 1;
130130

131131
if (categoryId === -1) {
132132
toastError('카테고리를 선택해주세요.');
@@ -153,6 +153,7 @@ function WriteSection({ mode, postId }: Props) {
153153
payload.append('post', postBlob);
154154

155155
try {
156+
setIsLoading(true);
156157
const res = await fetch(`${getApi}/posts`, {
157158
method: 'POST',
158159
credentials: 'include',
@@ -161,10 +162,17 @@ function WriteSection({ mode, postId }: Props) {
161162

162163
if (res.ok) {
163164
router.push('/community');
165+
} else {
166+
// 서버 에러 응답 처리
167+
const errorData = await res.json().catch(() => ({}));
168+
const errorMessage = errorData.message || `서버 오류가 발생했습니다. (${res.status})`;
169+
toastError(errorMessage);
164170
}
165171
} catch (err) {
166172
console.error('글작성 폼 작성 에러', err);
167-
return;
173+
toastError('네트워크 오류가 발생했습니다. 다시 시도해주세요.');
174+
} finally {
175+
setIsLoading(false);
168176
}
169177
};
170178

@@ -216,7 +224,7 @@ function WriteSection({ mode, postId }: Props) {
216224
return false;
217225
}
218226

219-
const categoryId = tabItem.findIndex((tab) => tab.label === formData.categoryName);
227+
const categoryId = tabItem.findIndex((tab) => tab.label === formData.categoryName) + 1;
220228
if (categoryId === -1) {
221229
toastError('카테고리를 선택해주세요.');
222230
return false;
@@ -275,14 +283,14 @@ function WriteSection({ mode, postId }: Props) {
275283
e.preventDefault();
276284
};
277285

278-
if (isEditLoading) <Spinner />;
286+
if (isEditLoading) return <Spinner />;
279287

280-
if (isLoading) <DetailSkeleton />;
288+
if (isLoading) return <DetailSkeleton />;
281289

282290
return (
283291
<>
284292
<form onSubmit={mode === 'create' ? handleSubmit : handleEditSubmit}>
285-
<CompleteBtn mode={mode} setEditDone={setEditDone} />
293+
<CompleteBtn mode={mode} setEditDone={setEditDone} isLoading={isLoading || isEditLoading} />
286294
<section>
287295
<FormTitle formData={formData} setFormData={setFormData} />
288296
<Category formData={formData} setFormData={setFormData} />

0 commit comments

Comments
 (0)