default.mp4
최애의포토는 당신의 ‘최애’ 순간들을 사진으로 담아 간직하고 공유할 수 있는 감성 기반의 포토 기록 플랫폼입니다. ‘가장 좋아하는 것’을 뜻하는 ‘최애’와 소중한 ‘사진’을 모은다는 의미에서 탄생했습니다. 일상 속에서 놓치고 싶지 않은 순간들, 나만의 취향과 감정을 표현하고 싶은 사진들, 최애의포토에 담아보세요. 흘러가는 SNS가 아닌, 당신만의 의미 있는 아카이브 공간에서 특별한 사진들을 모으고, 정리하고, 간직해보세요.
1️⃣ 카드 생성: 내가 좋아하는 사진을 온라인 공간에 올리고 저장할 수 있습니다.
2️⃣ 포인트 제도: 한 시간에 한 번, 랜덤 포인트를 받을 수 있고, 그 포인트로 카드를 거래할 수 있습니다.
3️⃣ 카드 거래: 내가 만든 카드를 판매하거나 다른 사용자가 만든 카드를 구입할 수 있습니다.
4️⃣ 카드 교환: 내 카드와 판매 중인 카드를 1:1 교환할 수 있습니다.
5️⃣ 알림 기능: 카드 거래·교환 시 실시간으로 알림을 받습니다.
6️⃣ 다양한 필터 기능: 상황에 따라 검색, 정렬, 필터링 기능을 제공합니다.
➡️ 다음은 백엔드 Git 저장소입니다.
🔧 GitHub Repository 보러가기
| 로그인 및 로그아웃 | 랜덤 포인트 | 포토카드 생성 | 필터, 무한스크롤 |
|---|---|---|---|
|
|
|
|
| 포토카드 판매 | 포토카드 수정 | 포토카드 판매 내리기 | 포토카드 구매 |
|---|---|---|---|
|
|
|
|
| 알림 확인 | 교환 요청 | 구매자의 교환 요청 취소 | 판매자의 교환 승인 및 취소 |
|---|---|---|---|
|
|
|
|
| Team Leader | Deputy Team Leader | Team Member | Team Member | Team Member | Team Member |
|---|---|---|---|---|---|
이지수 |
김다은 |
김우주 |
양성경 |
장원빈 |
홍성훈 |
- 초기 접속 시 진입
- 판매 등록된 포토카드 목록
- 포토카드 구매, 교환, 수정, 삭제
- 소셜 로그인 기능, 소셜 로그인 간편 회원가입
- 내가 보유한 포토카드 확인
- 나의 최애의 포토카드 업로드
- 사용자의 판매중인 포토카드 내역 확인
- 이전/신규 알림 확인
- 판매 등록된 포토카드 목록 조회
- 최신순 / 가격순 정렬 기능
- 검색 및 필터링 (키워드, 장르, 등급, 가격순)
- 무한 스크롤 로딩
- 포토카드 상세 정보 확인
- 구매 요청 / 교환 신청
- 포토카드 수정, 삭제 기능 (소유자만 가능)
- 거래 상태 확인 (판매 중 / 거래 완료 등)
- 구글 소셜 로그인 기능
- 최초 로그인 시 자동 회원가입 처리
- 사용자 정보 로컬 저장 및 JWT 인증 처리
- 현재 보유한 포토카드 목록 확인
- 현재 보유한 포토카드 개수 확인
- 검색 및 정렬 (키워드, 장르, 등급 필터)
- 이미지 업로드 (Cloudinary 연동)
- 등급 / 장르 / 가격 / 설명 입력
- 업로드 완료 시 마이갤러리에 반영
- 내가 판매 중인 포토카드 목록 확인
- 교환 제시 대기 중인 포토카드 확인
- 검색 및 정렬 (키워드, 장르, 등급, 판매 방식, 매진 여부)
- 거래 완료 처리 및 거래 히스토리 열람
- 구매 / 교환 / 불발 관련 알림 및 신규 알림 확인
- 읽은 / 안 읽은 알림 구분 및 상태 처리
🖼️ 이미지 업로드 방식 변경 (Multer → Cloudinary)
- 기존에는 Multer를 사용하여 이미지를 서버의 로컬 디렉토리에 저장함.
- Render에서 서버를 재배포하면 로컬 저장소가 초기화되어 이미지가 모두 삭제되는 문제 발생.
- Render와 같은 PaaS 환경은 **비영구적 파일 시스템 (ephemeral)**을 제공함.
- Multer는 기본적으로 서버의
uploads/폴더 등에 이미지를 저장 → 서버 재시작 시 삭제됨. - 이미지 손실로 인해 서비스 품질에 심각한 영향 발생 가능.
- Cloudinary와 같은 외부 이미지 호스팅 서비스를 활용하여 문제 해결.
- 프론트엔드에서 이미지 파일을 직접 Cloudinary에 업로드하고, 응답으로 받은
image URL을 백엔드에 전달. - 백엔드는 해당
URL만 DB에 저장하여 이미지 경로를 관리.
- 재배포나 서버 재시작과 관계없이 이미지가 안정적으로 유지됨.
gif,jpg,png등 다양한 이미지 포맷 업로드 가능.- 이미지 로딩 속도 및 품질 최적화도 Cloudinary에서 자동 처리됨.
Cloudinary 이미지 업로드 문제를 해결한 코드입니다:
export async function upLoadImage(file) {
const url = 'https://api.cloudinary.com/v1_1/[yourId]/image/upload';
const data = new FormData();
data.append('file', file);
data.append('upload_preset', 'primary-key');
try {
const res = await fetch(url, {
method: 'POST',
body: data,
});
if (!res.ok) {
throw new Error('Image Upload Failed!');
}
const result = await res.json();
return result;
} catch (error) {
console.error(error);
return null;
}
}🛍️ 구매 후 UI 즉각 반영 문제 (useEffect → React Query `useMutation`)
- 구매자가 ‘구매하기’ 버튼을 누르면 서버에 구매 요청이 전송되고, 잔여 수량(remaining)이 즉시 반영되어야 했음.
- 처음에는 useEffect를 통해 서버 데이터 변화를 감지하여 잔여 수량을 업데이트하려 했으나:
- 구매 직후 UI에 잔여 수량이나 총 가격이 즉시 반영되지 않는 문제 발생.
- 네트워크 지연 등으로 인해 UI가 한 템포 느리게 갱신되어 UX가 부자연스러움.
- useEffect는 상태나 props 변화에 따라 동작하지만:
- 외부 API 응답이 완료되는 시점과 React 렌더링 타이밍이 어긋남.
- 서버 데이터가 갱신되어도, 반영된 데이터를 받아오기까지 지연이 발생.
- 사용자 입장에서는 즉각적인 시각 피드백이 필요하지만, 기존 구조에서는 이를 구현하기 어려웠음.
- useMutation을 통해 구매 요청과 로컬 상태 변경을 하나의 흐름으로 통합.
- 다음과 같은 장점이 있음:
- 비동기 요청 수행 중 로딩 상태 표시 (isPending) 가능
- 요청 성공 시 로컬 상태(localRemaining)를 즉시 갱신 → UI 즉각 반영
- 실패 시 에러 처리 및 사용자 알림(UI 모달 등)이 간편
const { mutate, isPending } = useMutation({
mutationFn: () => storeService.purchaseCard(cardId, quantity),
onSuccess: (data) => {
setLocalRemaining((prev) => prev - quantity); // 즉시 UI 반영
if (onSuccess) onSuccess(data);
openStateModal(200, "구매", { grade, name: cardName, count: quantity });
},
onError: (err) => {
openStateModal(err.status || 400, "구매", { grade, name: cardName, count: quantity });
},
});mutate()호출 시 비동기 요청 실행- 성공 시 localRemaining을 직접 갱신하여 화면이 즉시 반응
- 실패 시 적절한 에러 모달로 사용자에게 피드백 제공
- UI 반응 속도 개선 → 구매 직후 잔여 수량과 총 가격 즉시 반영
- useEffect에 의존하지 않아 렌더링 타이밍과 상관없이 안정적인 반응
- 비동기 요청과 상태 변경을 한 곳에서 처리함으로써 코드 가독성과 유지보수성 향상
이지수
김다은
장원빈
양성경
김우주
홍성훈













