[FE] 테크 스펙 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki

작성일 : 25 04 28


배경 (Background)

메인 페이지와 앨범 페이지에서 UX를 높이는 것을 목표로 한다.
앨범 생성 시에 효율적으로 S3 서버로 전송하여 URL을 생성한다.

목표가 아닌 것 (Non-goals) (Optional)

이번 프로젝트에서 다루지 않는 내용.

  • 사진 최대 전송 개수 개선
  • 커뮤니티 기능
  • 전체 사진 보관함 기능
  • 내 활동 기능

설계 및 기술 자료 (Architecture and Technical Documentation)

페이지

/main : 메인 페이지

  • 주요 기능 : 지도 사진 클러스터링, 앨범 목록.

  • 사용 컴포넌트

    • Map (지도)
    • AlbumList (앨범 목록)
    • ImageGrid
    • AlbumThumbnail
  • 데이터 로딩 시점

    • 페이지 진입 시 : 전체 앨범 목록 조회GET /api/album/monthly?yearMonth={yearmonth} 호출
    • 앨범 선택 시 : 앨범 요약 조회 GET /api/album/{albumId}/summary

/album : 앨범 페이지

  • 주요 기능 : 앨범 요약 정보 확인, 사진 기록 표시, 카테고리 선택, 링크 공유

  • 사용 컴포넌트

    • Card
    • Collection
    • Highlight
    • Header
  • 데이터 로딩 시점
    페이지 진입 시 앨범 상세 조회GET /api/album/{albumId} 호출

  • 라우팅 : /album/{albumId}

/collection : 보관함 페이지

  • 주요 기능 : 선택 사진 제거, 이미지 자세히 보기
  • 사용 컴포넌트 :
    • 이미지 컴포넌트
  • 데이터 로딩 시점 : 페이지 진입 시GET /api/album/{albumId} (최신 데이터 갱신을 위해서 로딩 후 필터링)
  • 라우팅 : /album/{albumId}/collection/{collectionId}

/album/editor : 앨범 생성 페이지

  • 주요 기능 : 사진 추가. 앨범 생성
  • 사용 컴포넌트
    • Input
    • ImageGrid

/login

  • 주요 기능 : OAuth를 이용한 로그인

/share : 링크 전송시 미리보기 페이지

주요 컴포넌트

main 페이지

앨범 목록 컴포넌트

  • 개요

    • 역할 : 월별 앨범 목록 표시. 선택 및 앨범 상세 페이지로 진입. 무한 스크롤 기능을 수행
  • Props & Interface

    • startMonth : 월별로 앨범(썸네일)을 보여준다. 현재 월을 첫번째 월로 가진다. 컴포넌트 마운트 시, 현재 날짜를 기준으로 설정한다.
    • endMonth : 현재 로딩된 마지막 월을 나타낸다. (25년 3월 -> 2503 과 같이 나타낸다.)
    • isEndPage : 사용자가 가지고 있는 앨범을 더 로드할 수 있다면 true이다.
interface AlbumList {
	startMonth : number
	endMonth : number
	isEndPAge : boolean
	onAlbumSelect?: (albumId: string) => void;
	onMonthChange?: (newMonth: number) => void;
}
  • 내부 상태 & 이벤트
    • month : 현재 주시하고 있는 월
    • useInfiniteQuery : 무한 스크롤에 이용
    • onAlbumSelect : 선택 앨범 변경 시 부모에게 알리는 콜백 함수
    • onMonthChange : 주시하고 있는 월이 변경되었을 때, 부모에게 알리는 콜백 함수

ImageGrid 컴포넌트

  • 개요

    • 역할 : 이미지 그리드 표시, DnD를 사용한 순서 변경 기능, Lazy Loading 기능
    • (앨범 상세 페이지에서도 재사용?)
  • Props & Interface

    • row : 열 개수 (default 4)
    • gap : 이미지 사이 간격 (default 0)
    • images : 이미지 목록
    • isEmpty : 비어있는 경우 다른 UI 표시.
interface ImageGrid{
	row : number
	gap? : number
	isEmpty : boolean
	images: ImageItem[];
	onImageClick?: (imageId: string) => void;
	loadingPlaceholder?: ReactNode;
}

interface ImageItem {
	id: number;
	url: string;
	isLoaded?: boolean;
	shouldLoad?: boolean;
}
  • 내부 상태 & 이벤트
    • isLoading : 로딩 상태
    • onImageClick : 이미지 클릭 시 부모에게 알리는 콜백 함수

앨범 썸네일 컴포넌트 :

  • 개요

    • 역할 : 앨범의 대표 사진을 표시, 공동작업자가 있는 경우 표시한다.
  • Props & Interface

    • imageUrl : 썸네일로 표시할 이미지
    • hasCoworkers : 개인 앨범인지, 공유 앨범인지 나타낸다.
    • coworkerIds : 공동작업자의 정보 목록. 공동 작업자의 프로필 사진을 표시하는데 사용한다.
    • albumId : 앨범으로 이동 시 사용한다.
    • isSelected : 앨범이 선택된 상태인지 나타냄. 공유 받은 사람의 초기 화면이나, 되돌아왔을 때에 사용
interface AlbumThumbnail {
	imageUrl : string
	hasCoworkers : boolean
	coworkerIds : number[]
	albumId : number;
	onSelect?: (isSelected: boolean) => void;
	isSelected?: boolean;
}

내부 상태 & 이벤트

  • isSelected : 선택 시 지도 컴포넌트를 변경시키고, 다시 클릭 시, 앨범 페이지로 이동한다.
  • isLoading : 이미지 로딩 상태
  • hasError : 오류 상태
  • onSelect : 선택되었을 때, 부모 컴포넌트에게 알리는 콜백함수

지도 컴포넌트

  • 개요
    • 역할 : 지도 스크롤 및 확대 등 조작, 마커 클러스터 표시, 대표 사진 표시, 경로 표시
  • Props & Interface
    • isAlbumSelected : 앨범이 선택되었을 경우와 아닌 경우, UI의 차이를 주기 위한 속성
    • selectedAlbumId : 현재 선택된 앨범의 Id
    • photoLocations : 앨범에 포함된 사진들의 위치 및 표시에 필요한 데이터
    • initialCenter : 초기 중심 좌표
    • initialZoom : 초기 줌 레벨
    • clusterThreshold : 클러스터를 하나로 모으는 단위
    • onMapBoundsChange : 맵 바운더리가 변경되었을 때 로직을 처리하는 함수
interface Map{
	isAlbumSelected : boolean;
	selectedAlbumId?: number;
	photoLocations?: PhotoLocation[];          
	initialCenter?: Coordinates;
	initialZoom?: number;    
	clusterThreshold?: number;   
	onMapBoundsChange?: (bounds: MapBounds) => void; 
}

interface PhotoLocation {
  id: string;             
  coordinates: Coordinates;  
  thumbnailUrl: string;     
  timestamp?: number;               
}

interface Coordinates {
  latitude: number;
  longitude: number;
}

interface MapBounds {
  north: number;
  south: number;
  east: number;
  west: number;
}
  • 상태 및 이벤트
    • center : 현재 지도 중심 좌표
    • zoom : 현재 지도 줌 레벨
    • clusters : 클러스터 목록
    • bounds : 현재 지도 경계
    • isLoading : 로딩 상태

album 페이지

앨범 컴포넌트

  • 개요

    • 역할 : 앨범 내의 이미지 관리. 데이터 페칭을 수행.
  • Props & Interface

interface Album {
	id : number
	title : string
	collections : Collection[];
	imageCount: number;
	coverImageUrl?: string;
	owner: User;
	collaborators?: User[];
	isShared: boolean;
}
  • 내부 상태 & 이벤트
    • collections : 카테고리 별 보관함

보관함 컴포넌트 :

  • 개요

    • 역할 : 태그에 해당하는 사진을 보유하며, 삭제 기능을 수행한다.
  • Props & Interface

    • id : 보관함 인덱스
    • title : 보관함 제목 (태그명 또는 검토할 항목)
    • images : 포함된 사진 목록
    • onRemove : 사진 삭제 시, 부모에게 알리는 콜백
interface Collection {
	id : number
	title : string
	images : Image[]
	onRemove?: (imageId : number) => void;
}
  • 내부 상태 & 이벤트
    • images : 이미지 목록
    • handleRemove(selectedList) : 사진 삭제 시 부모 컴포넌트에 알림

Card 컴포넌트

  • 개요

    • 역할 : 대표 사진 및 요약 정보와, 지도를 보여준다.
  • Props & Interface

interface Card {
	imageUrl : boolean
	center : Coordinates
}
  • 내부 상태 & 이벤트
    • isFront : 카드 앞 뒷면 구분
    • handleFlip : 카드 상태 변경 처리

Image 컴포넌트

  • 개요
    • 역할 : 이미지 표시, 이미지 최적화 기능 수행
    • Next.js 의 이미지 컴포넌트 활용
  • Props & Interface
    • imageId : 이미지 식별자
    • imageUrl : 이미지 경로
interface Image{
	id : number
	url : string
	index : number
}

상태 관리 전략 (State Management Strategy)

Global State (Zustand Stores) :

  • 중앙 스토어
    • 사용자 정보
    • 인증 상태
    • 로딩 상태

메인 페이지 albumStore imageStore locationStore

앨범 페이지 - UI 상태 - 선택된 이미지 목록, 개수 - 편집 모드 상태 - 모달 상태