내 취향이 담긴 투표로 함께 패션 트렌드를 만들어요!
![]() |
![]() |
![]() |
![]() |
![]() |
|---|
매일매일 새로운 스타일에 투표하고, 가장 사랑받은 룩이 오늘의 페이피(FA:P)로 빛나요.
투표하며 발견한 나만의 취향, 한 눈에 모아보는 재미도 놓치지 마세요.
FADE_Demo.mp4
🔊 소리가 포함된 영상입니다
| 영상제작 | 김 주현(@sangpok) | |
|---|---|---|
| 나레이션 | 서성희 아나운서 | |
| 제작 툴 | Adobe Premiere Pro | Adobe After Effects |
FADE 서비스는 스위프 5기에서 진행된 서비스로, 사용자가 패션 투표를 통해 오늘의 패션왕, FA:P(페이피)를 선정하고, 패션 트렌드와 본인의 취향을 모아볼 수 있는 서비스입니다.
스위프
PM·디자이너·개발자가 6주 동안 한 팀으로 웹 개발 프로젝트를 진행하며, 아이디에이션부터 출시까지 함께 경험하고 성장할 수 있는 프로그램입니다.
FADE는 크게 투표와 아카이브 기능으로 나눌 수 있습니다.
- 매일 최대 10개의 투표를 통해 당일의 FA:P을 선정합니다.
- 투표는 간편한 스와이프 기능을 통해 이루어집니다.
- 선정된 FA:P을 월별로 아카이브하여 한눈에 확인할 수 있습니다.
- 또한 서비스에 올라온 모든 패션 사진을 열람할 수 있습니다.
- 마음에 드는 사용자(페이더)를 구독하고, 저장하고 싶은 패션 사진을 북마크할 수 있습니다.
- 구독한 페이더들의 피드를 모아볼 수 있습니다.
- 내 피드, 내 투표 내역, 내 북마크 등 개인과 관련된 정보를 확인할 수 있습니다.
총 6주의 기간으로 진행되었으며 프로젝트 발표 전까지 2주의 기간 동안 버그 수정 및 최적화를 진행했습니다.
| 작업 분류 | 기간 | 요약 |
|---|---|---|
| 기획 및 디자인 | 6월 17일 ~ 7월 12일 | 약 4주, 26일 |
| 프론트 개발 | 7월 12일 ~ 28일 | 약 2주, 17일 |
| API 연동 시작 | 7월 26일 ~ 28일 | 3일 |
| 버그 수정 및 최적화 | 7월 28일 ~ 8월 9일 | 약 2주, 12일 |
- 프로젝트 발표: 8월 10일 토요일
프론트엔드 작업자: 김 주현(@sangpok), HM(@dxdlar)
HM이 웹 개발이 처음인 관계로 전체적인 프로젝트 주도 및 기능 개발을 김 주현(@sangpok)이 담당하였습니다.
프로젝트 전반적인 진행은 기획자 박정원이 맡았고, 개발 전반에 대한 진행은 김 주현이 주도하였습니다.
| 김 주현 | HM |
|---|---|
| 개발 관련 작업 주도 | 서비스 잠금 화면 작성 |
| 프론트 전반 개발 | 툴팁 UI Component 작성 |
| HM 멘토링 | 서비스 잠금 로직 기초 작성 |
프로젝트 진행을 위해 김 주현이 HM의 웹 개발에 대한 멘토링을 진행하였습니다. HTML, Javascript, CSS에 대한 웹 기초에 대한 지식을 쌓고, React에 대한 기본 개념을 쌓은 후 React UI 컴포넌트 하나를 작성하는 것을 목표로 두고 멘토링을 진행하였습니다.
| 과제 목록 | 학습 기록 |
|---|---|
![]() |
![]() |
FADE 서비스에서 활용한 기술은 아래와 같습니다.
| Core | |||
|---|---|---|---|
| Styling | Radix UI |
Framer Motion |
|
| State Mgmt | Zustand |
React Query |
|
| Form Valid. | React Hook Form |
Zod |
|
| Network Lib | Axios |
||
| Mocking | MSW |
||
| Deploy | Vercel |
||
기술 스택을 선정할 때 고려한 것은 다음와 같습니다.
- 서비스의 성격상 모바일 어플리케이션 서비스에 가까우므로 SPA로 개발하고자 하였습니다.
React,React Router DOM,PWA
- 강타입 언어를 해온 HM은 웹 개발이 처음이라 Next.js까진 러닝 커브가 높다고 판단했습니다.
React,Typescript
- 모바일 어플리케이션 느낌을 위해 부드러운 애니메이션을 제공하고자 했습니다.
Framer Motion
- 짧은 개발 기간이므로 생산성이 높고 익숙한 기술을 사용하고자 했습니다.
Radix UI,TailwindCSS,React Query,Axios,React Hook Form,Zod,Zustand
- 개발 공백을 최대한 줄이기 위해 API Mocking을 활용하고자 했습니다.
MSW
FADE가 제공하고 있는 서비스 기능들에 대해서 소개합니다.
최초로 서비스에 접속했을 때 사용자에게 보여주는 화면입니다.
![]() |
![]() |
![]() |
|---|
서비스에 대해 소개하는 이미지를 Dissolve Transition으로 3초 마다 변경합니다. NavItem 클릭 시 인터벌을 초기화합니다.
카카오 계정으로 로그인할 수 있는 소셜 로그인 버튼입니다.
로그인 없이 서비스를 둘러볼 수 있는 둘러보기 기능입니다. 해당 기능은 API Mocking으로 동작하며, 실제 데이터가 아니기 때문에 일부 기능은 실제 화면과 다소 차이가 있습니다.
![]() |
![]() |
![]() |
|---|---|---|
| - | 서비스 둘러보기 모드 시, 환경을 제어할 수 있는 테스트 버튼이 오버레이됩니다. | - |
FADE 서비스에 로그인 및 회원가입할 수 있는 기능입니다.
![]() |
![]() |
![]() |
![]() |
|---|
FADE 서비스에 가입하기 위해서는 서비스 약관 동의서, 개인정보 수집 및 이용 동의서, 만 14세 이상에 동의해야 합니다.
FADE 서비스에서 이용할 정보를 기입합니다.
오늘의 FA:P(페이피)를 선정하기 위해 투표를 진행합니다.
FA:P(페이피)
그날 최고의 패션으로 선정된 페이더를 뜻합니다.
페이더
FADE 서비스를 사용하는 유저를 뜻합니다.
![]() |
![]() |
![]() |
![]() |
![]() |
|---|
![]() |
![]() |
![]() |
![]() |
|---|
| 관련 PR | ✨ 투표 탭 기본 컴포넌트 구현 (#51) |
|---|---|
| ✨ 투표 탭 투표 기능 구현 (#71) | |
| ✨ 투표 정보 Store 작성 (#77) | |
| ✨ 투표 선택지 목록 조회 API 연동 (#101) | |
| ✨ 변경된 Auth API 반영 & 투표 API 연동 & 구독 API 연동 & 북마크 API 연동 (#139) | |
| 🐛 투표 선택지 커버 FADE OUT 블링킹 이슈 (#180) | |
| 🐛 투표 후보군 목록 미존재 시 토스트 이슈 & S3 이미지 쿼리 파람 추가 (#248) |
서비스 내에서 아직 투표하지 않은 패션 사진들을 가져옵니다.
- 투표하지 않은 패션 사진
- 사용자가 신고한 사진 제외
- 최신순
- 랜덤
투표 후보군 목록에 대해 투표를 진행합니다.
- FA:P에 선정하고 싶으면 오른쪽으로 스와이프(FADE IN), 선정하고 싶지 않으면 왼쪽으로 스와이프(FADE OUT)
- 또는 FADE IN 버튼, FADE OUT 버튼을 동해서도 가능
![]() |
|---|
- 해당 패션 사진을 올린 페이더가 마음에 든다면 구독할 수 있습니다.
- 또는, 해당 패션 사진이 마음에 든다면 북마크할 수 있습니다.
- FADE 서비스에 부적절한 사진이라면 신고할 수 있습니다.
![]() |
![]() |
|---|
| 관련 PR | ✨ 신고 Bottom Sheet 구현 (#47) |
|---|
- 사용자가 투표한 결과를 서버에 전송합니다.
FA:P로 선정된 패션 사진들이나 FADE 서비스 내 업로드된 모든 패션 사진을 조회할 수 있습니다.
![]() |
![]() |
![]() |
![]() |
![]() |
|---|
| 관련 PR | ✨ 아카이브 탭 레이아웃 구현 (#103) |
|---|
월별로 FA:P에 선정된 패션 사진들을 조회할 수 있습니다.
![]() |
![]() |
|---|
| 관련 PR | ✨ FA:P 아카이브 조회 API 연동 (#141) |
|---|---|
| 🐛 FA:P 아카이브 달력 말일 오작동 이슈 (#178) |
오늘 하루 최초 아카이브 탭에 접속 시 어제 선정된 FA:P를 소개합니다.
| 관련 PR | ✨ 어제의 FA:P 모달 구현 (#132) |
|---|---|
| ✨ 어제의 FA:P 동작 구현 (#222) |
FADE 서비스 내에 업로드된 모든 패션 사진을 조회할 수 있습니다.
성별 또는 스타일로 패션 사진을 필터링할 수 있습니다.
| 관련 PR | ✨ 패션 전체보기 필터 Dialog 작성 (#114) |
|---|---|
| ✨ 패션 전체보기 조회 API 연동 (#143) |
FADE 서비스에 가입한 페이더들을 계정명으로 검색할 수 있습니다. 선택 시 유저 피드 페이지로 이동합니다.
![]() |
|---|
| 관련 PR | ✨ 계정 검색 UI 구현 (#116) |
|---|---|
| ✨ 계정 검색 > 최근 검색 로직 수정 (#207) |
최근에 검색한 페이더 목록을 확인할 수 있습니다.
본인의 패션 사진을 업로드할 수 있습니다. 업로드한 패션 사진은 자동으로 투표 후보 목록으로 올라가며, 내 피드에 추가됩니다.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|---|
| 관련 PR | ✨ 사진 업로드 화면(UI) 작성 (#33) |
|---|---|
| ✨ 사진 업로드 API 연동 (#99) |
패션 사진을 선택하여 서버에 업로드합니다. 중복 사진은 업로드할 수 없습니다.
업로드할 패션 사진과 관련된 스타일 뱃지를 선택합니다.
업로드할 패션 사진에 대한 착장 정보를 기입할 수 있습니다.
| 관련 PR | ✨ 착장 정보 필드 디자인 변경 대응 (#55) |
|---|
본인이 구독한 페이더 목록을 확인하거나 그들의 피드를 최신순으로 모아볼 수 있습니다.
![]() |
![]() |
|---|
| 관련 PR | ✨ 구독 탭 레이아웃 화면 작성 (#105) |
|---|
본인이 구독한 페이더 목록을 확인할 수 있습니다.
| 관련 PR | ✨ 구독 목록 UI 구현 (#118) |
|---|
본인이 구독한 페이더들의 피드들을 최신순으로 모아볼 수 있습니다.
본인과 관련된 정보를 확인하거나 수정할 수 있습니다.
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
|---|
| 관련 PR | ✨ 마이페이지/계정 관리/회원 탈퇴 화면 작성 (#110) |
|---|
본인에 대한 정보를 수정하거나 FADE 서비스에서 탈퇴할 수 있습니다.
프로필 사진, 계정명, 성별을 수정할 수 있습니다.
FADE 서비스에서 탈퇴할 수 있습니다.
![]() |
![]() |
![]() |
|---|
FADE 서비스에 올린 본인의 패션 사진들을 확인할 수 있습니다.
![]() |
![]() |
![]() |
![]() |
|---|
FADE IN 갯수, 북마크 갯수, 신고 횟수를 확인할 수 있습니다.
업로드한 피드에 대해 사진을 제외하고 정보를 수정할 수 있습니다.
업로드한 피드를 삭제할 수 있습니다.
본인에 대한 자기소개를 수정할 수 있습니다.
![]() |
|---|
그동안 투표한 내역을 확인할 수 있고, FADE IN한 패션 사진만 확인할 수도 있습니다.
| 관련 PR | ✨ 투표 내역 화면 작성 (#129) |
|---|---|
| ✨ 투표 내역 API 연동 (#154) |
북마크한 패션 사진들을 확인할 수 있습니다.
FADE 서비스에 대한 정책을 확인할 수 있습니다.
FADE 서비스에서 로그아웃할 수 있습니다.
FADE 서비스의 정체성 유지하고 유의미한 트렌드 데이터를 수집하기 위해 유저는 매일 투표 10개를 해야 서비스를 이용할 수 있습니다.
![]() |
|---|
| 관련 PR | ✨ 서비스 lock 화면 구현 (#134) |
|---|---|
| ✨ Tooltip 위치 조정 & 투표 미진행 시 서비스 Lock 기능 구현 (#145) | |
| 🐛 서비스 잠금 화면 Lock UI 수정 (#244) |
해당 페이더에 대한 정보와 올린 피드를 확인할 수 있습니다.
![]() |
![]() |
|---|
FADE 서비스를 만들며 고민한 주요 포인트를 화면 관련과 기능 관련으로 나누어 설명하고자 합니다.
FADE 서비스의 화면은 보다 높은 웹접근성과 생산성을 위해 Radix UI를 선택하였습니다. 원하는 스타일을 구현하기 위해 TailwindCSS 사용하였고, 부드러운 애니메이션을 위해 Framer Motion을 사용하였습니다. 또한, 다양한 환경에서 최적의 UI를 구현하기 위해 반응형 디자인을 고려하며 구현하였습니다. 디자인 단계에서 반응형 디자인을 고려하지 않았으나, 프론트엔드 단에서 최대한 구현하고자 하였습니다.
데스크탑 환경(Window, Mac), 모바일 디바이스 환경(iOS, Android) 각각에서 Google Chrome, Safari, PWA에 대한 호환성을 테스트하였습니다. 보다 자세한 테스트 환경은 다음과 같습니다.
- iPhone 12 Mini
- iPhone 12
- iPhone 13 Pro
- iPhone 13 Pro Max
- iPhone 15
디자인 단계에서 공통 UI 컴포넌트를 정의하지 않고 디자인하였으나, 프론트엔드 단에서 자주 쓰이는 UI 컴포넌트를 공통 UI 컴포넌트으로 빼내 서비스 내 공통감과 생산성을 높였습니다. 아래 외에도 여러 UI 컴포넌트가 있으나, 가장 많이 쓰인 주요 UI 컴포넌트를 소개합니다.
FADE에서 사용하는 Button 컴포넌트입니다. 다양한 스타일과 인터랙션을 지원하는 유연한 UI 요소입니다. forwardRef를 사용하여 외부에서 ref를 통해 버튼 요소에 직접 접근할 수 있게 합니다.
![]() |
|---|
- 다양한 스타일 옵션: TButton 타입을 통해 버튼의 변형(variants), 크기(size), 인터랙션(interactive) 설정 가능
- 변형(variants): primary, secondary, white, destructive, ghost, outline
- 크기(size): default, icon
- 인터랙션(interactive): default, onlyScale, onlyColor
- 접근성 고려: aria-disabled 속성 사용
- 반응형 디자인: 터치 및 포인터 디바이스에 대한 최적화
- variants: 버튼의 시각적 스타일 (기본값: 'primary')
- size: 버튼의 크기 (기본값: 'default')
- interactive: 버튼의 상호작용 방식 (기본값: 'default')
- className: 추가적인 CSS 클래스
- children: 버튼 내부 컨텐츠
- 기타 HTML 버튼 요소의 속성들 (ButtonHTMLAttributes)
- 기본 클래스: 모든 버튼에 공통 적용
- variants 기반 클래스: 배경색, 텍스트 색상 등 스타일 변경
- 상태 기반 클래스: interactive prop과 variants prop에 따른 상태별 스타일
- 크기 기반 클래스: size prop에 따른 패딩 조정
- aria-disabled 속성을 통한 버튼 상태 전달
- 디바이스 유형별 최적화된 상호작용
- 비활성화 상태에 대한 시각적 피드백
<Button variants="primary" size="default" interactive="default">
Click me
</Button>
<Button variants="outline" size="icon" interactive="onlyScale">
<MdChevronLeft />
</Button>| 관련 PR | ✨ 계정 관련 및 피드 관련 폼 리팩토링 & 컴포넌트 정리 및 디자인 요소 통일 (#136) |
|---|
이미지 로딩 상태 관리, 에러 처리, 반응형 이미지 지원 등 다양한 기능을 제공합니다.
- 상태 관리
- 로딩 중(isPending)과 에러(isError) 상태를 관리합니다.
- 각 상태에 따라 적절한 UI를 렌더링합니다.
- 애니메이션 효과
- Framer Motion을 사용하여 이미지 로드 시 부드러운 페이드인 효과를 적용합니다.
- 반응형 이미지
- createSrcSet 함수를 사용하여 로컬 이미지에 대한 srcset을 생성합니다.
- 원격 이미지의 경우 쿼리 파라미터를 사용하여 이미지 크기와 품질을 조정합니다.
- 이미지 크기 조정
- 'cover', 'contain', 'fit' 옵션을 통해 이미지 크기 조정 방식을 제어합니다.
- 드래그 방지
- PreventImageDragging 컴포넌트를 사용하여 이미지 드래그를 방지합니다.
- 에러 처리
- 이미지 로드 실패 시 에러 아이콘을 표시합니다.
- 로딩 인디케이터
- 이미지 로드 중 스피너를 표시합니다.
- src: 이미지 소스 URL (필수)
- className: 추가 CSS 클래스 (선택적)
- alt: 이미지 대체 텍스트 (선택적)
- size: 이미지 크기 조정 방식 ('cover' | 'contain' | 'fit', 기본값: 'cover')
- local: 로컬 이미지 여부 (boolean, 기본값: false)
- children: 추가 컨텐츠 (선택적)
- 로컬 이미지: createSrcSet 함수로 srcset 생성
- 원격 이미지: 쿼리 파라미터로 크기와 품질 조정 (예: ?w=720&q=10)
| 관련 PR | ✨ 계정 관련 및 피드 관련 폼 리팩토링 & 컴포넌트 정리 및 디자인 요소 통일 (#136) |
|---|---|
| 🩹 이미지 로딩 감지 로직 변경 (#291) |
보다 다양한 환경에서 의도한 UI가 표시될 수 있도록 크로스 브라우징을 구현하고자 했습니다.
레티나 디스플레이 등 고해상도 디바이스에 대응하기 위한 이미지를 적용하였습니다.
| 관련 PR | 📱 서비스 내 이미지 DPR 대응 (#214) |
|---|
모바일 디바이스 환경에서의 애니메이션에 대한 UX 경험이 이어질 수 있도록 부드러운 애니메이션을 구현하였습니다. Expo 타입의 모션감을 차용하였습니다.
![]() |
|---|
/** Motion Config */
const transitionConfig: Transition = {
ease: [0.16, 1, 0.3, 1],
duration: 0.5,
};화면 로드 시 등장 애니메이션을 구현하였습니다.
| 관련 PR | 💫 페이지 로드 트렌지션 추가 (#214) |
|---|
마이크로인터렉션을 통해 서비스의 완성도를 높였습니다.
내부 테스트 및 QA를 통해 UX를 최적화하였습니다.
| 관련 PR | ♻️ 피드 스크롤링 애니메이션 삭제 (#305) |
|---|---|
| 🐛 모바일 환경에서 스크롤링 이슈 수정 (#308) |
Lazy Import 및 Data Pending 상태일 때 Skeleton UI를 적용하여 사용자들에게 레이아웃 배치를 알려주었습니다.
| 관련 PR | 💄 스켈레톤 UI 작성 (#209) |
|---|
폼 제출 시 Submit 버튼을 포함한 입력 필드들이 Disabled됩니다. Submit 버튼에 Spin Loader를 표시하여 제출 중임을 알려주었습니다.
네트워크 딜레이에 대해 진행되고 있다는 안정감을 위해 시각적 진행도를 표시했습니다.
const [fakeProgress, setFakeProgress] = useState(10);
useEffect(() => {
const randomInterval = 500 + Math.floor(Math.random() * 500);
const timerId = setInterval(() => {
const randomProgressValue = Math.floor(Math.random() * 25);
setFakeProgress((prevValue) => prevValue + randomProgressValue);
}, randomInterval);
return () => clearInterval(timerId);
}, []);FADE 서비스 내에서 사용하고 있는 모달 종류는 Full Screen Dialog, Bottom Sheet, Component Modal, Confirm입니다. ModalProvider에서 모달 종류를 받아 AnimatedDialog에 넘겨주며, AnimatedDialog는 종류에 따라 적절하게 렌더링합니다.
Promise-based로 작성하여 Imperative하게 호출할 수 있습니다. 덕분에 여러 모달 시퀀스가 있거나 모달 반환값이 필요할 때 적절하게 제어할 수 있었습니다.
| 관련 PR | ✨ 사진 업로드 화면(UI) 작성 (#33) |
|---|---|
| ♻️ Promise-based Modal Pattern 리팩토링 (#61) |
모달 종류에 따라 적용할 수 있는 애니메이션을 지정하였습니다.
| slideUp | slideInFromRight | showAtCenter |
|---|---|---|
| 이미지 | 이미지 | 이미지 |
export type AnimateType = 'slideUp' | 'slideInFromRight' | 'showAtCenter';
const variantsMap: Record<AnimateType, Variants> = {
slideUp: slideUpVariants,
slideInFromRight: slideInFromRightVariants,
showAtCenter: showAtCenterVariants,
};기능과 관련된 기술 포인트를 소개합니다.
서버 측 API가 완성될 때까지 대기해야 하는 개발 공백 상태를 최대한 줄여서 작업할 수 있었습니다.
단 한 줄의 코드를 통해 Mocking 상태를 제어할 수 있었습니다.
| 관련 PR | 🔧 MSW를 사용한 API Mocking 환경 설정 (#9) |
|---|---|
| 🔧 Mocking API 기본 설정 (#17) | |
| ✨ API Mocking 버튼 추가 (#164) | |
| ✨ API Mocking 데이터 및 로직 수정 & Members 관련 API 연동 & 자잘한 수정 (#172) | |
| ✨ 서비스 둘러보기 기능 추가 (#284) |
각 API 응답 DTO에 따라 더미 데이터를 생성하는 Utility를 생성하였습니다.
같은 정보를 생성하거나 수정하는 폼에 대해 공통 스키마를 생성하여 필요한 필드를 선택하여 또 다른 스키마를 만드는 방식을 이용하여 폼을 관리하였습니다.
| 관련 PR | ✨ 내 피드 수정 기능 추가 (#218) |
|---|
서비스 내에서 발생하는 에러를 핸들링하였습니다.
| 관련 PR | ✨ API 실패 응답 핸들링 로직 작성 (#96) |
|---|
- 최초 이미지 업로드 시 업로드 가이드 바텀 시트 노출
- 이미지 선택 시 파일 해싱값을 Presigned URL 반환해주는 API에 담아 보냄 2-1. 해당 파일 해싱값이 존재 시(중복된 파일일 시), Toast 호출 2-2. 미존재 시 S3 Presigned URL과 attachmentId 응답 후 3번 진행.
- 받은 Presigned URL에 업로드 3-1. 실패 시 Toast 호출
| 관련 PR | ✨ 사진 업로드 API 연동 (#99) |
|---|
서비스 내에서 무한 스크롤 위주로 동작하는 기능이 많아 이에 대한 훅을 작성하였습니다.
Web API MutationObserver와 IntersectionObserver를 활용하였습니다.
| 관련 PR | ✨ 패션 전체보기 조회 API 연동 (#143) |
|---|---|
| ♻️ 무한스크롤 로직 동작 수정 (#212) |
API에서 응답하는 DTO에 따라 모델 타입을 선언하였고, 공통 DTO와 상속받는 DTO를 구분하여 유지보수를 유리하게 만들었습니다.
| 관련 PR | ✨ API Mocking 데이터 및 로직 수정 & Members 관련 API 연동 & 자잘한 수정 (#172) |
|---|
웹앱으로 더 즐겁고 편안한 경험을 제공하기 위해 PWA 환경을 제공합니다.
| 관련 PR | ♿️ SEO 관련 메타 정보 기입 (+PWA) (#81) |
|---|---|
| ♿️ 온보딩 페이지 PWA 환경 대응 (#93) | |
| ♿️ 오픈 그래프(Open Graph) 이미지 교체 및 Screenshots 추가 (#278) |











































































