Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[2단계 - 상세 정보 & UI/UX 개선하기] 초코(강다빈) 미션 제출합니다. #171

Merged
merged 105 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
58a3ce0
feat: eslint와 prettier 기본 세팅
00kang Mar 19, 2024
5661a8a
feat: eslint, prettier 패키지 설치
Jaymyong66 Mar 19, 2024
022dbf2
feat: eslint error를 warn으로 설정 변경
00kang Mar 19, 2024
f3135ea
chore: ts-eslint parse 설정
00kang Mar 19, 2024
52f9c3a
feat: image 소스 파일 추가
00kang Mar 19, 2024
553471b
feat : css - common.css, reset.css 추가
00kang Mar 19, 2024
a9e23b3
feat: index.html 템플릿 설정
00kang Mar 19, 2024
a12194e
docs: 기능 요구 사항 정리
00kang Mar 19, 2024
40846a2
feat: mockingData 설정
00kang Mar 19, 2024
00f7a69
feat: MovieCard 컴포넌트 구현
00kang Mar 19, 2024
dab4109
chore: eslint - browser env : true로 변경
Jaymyong66 Mar 19, 2024
c67458d
feat: mockingData 20개로 업데이트
Jaymyong66 Mar 19, 2024
716b456
chore: webpack-dev-server - 설정 및 이미지 파일 경로
Jaymyong66 Mar 19, 2024
4e07ad5
refactor: MovieCard 메서드 분리
Jaymyong66 Mar 19, 2024
5057566
feat: index.js에 css import
Jaymyong66 Mar 19, 2024
2bbc6f4
feat: MoreButton 컴포넌트
Jaymyong66 Mar 19, 2024
da29495
feat: API에서 받아온 데이터 렌더링
Jaymyong66 Mar 20, 2024
f74a933
feat: MovieCard 영화 데이터 주입
Jaymyong66 Mar 20, 2024
0b51801
feat: 더보기 버튼 클릭 시, 다음 페이지 영화 렌더링
Jaymyong66 Mar 20, 2024
568b3c4
feat: 영화 검색 기능
Jaymyong66 Mar 20, 2024
e2b5ccc
feat: skeleton UI 적용
Jaymyong66 Mar 20, 2024
5622b4c
feat: 영화 검색 기능 추가, page가 더 있을 때에만 더보기 버튼 렌더링
Jaymyong66 Mar 21, 2024
0696960
feat: 검색 결과가 없을때, UI 표시
Jaymyong66 Mar 21, 2024
04750f0
feat: 현재 영화 리스트에 따른 리스트 Title 변경
Jaymyong66 Mar 21, 2024
1ca0616
feat: 응답에 따른 에러 처리
Jaymyong66 Mar 21, 2024
97c0763
fix: 검색어 입력 후 Enter 누를 시, 두번 event가 발생하는 버그 수정
Jaymyong66 Mar 21, 2024
98ee73b
fix: 검색어 입력마다 presentPage count 리셋
Jaymyong66 Mar 21, 2024
22dc6ad
fix: skeleton li element가 완전히 제거되지 않는 버그 수정
Jaymyong66 Mar 21, 2024
b09291f
feat: 검색 후 home으로 돌아올 시, 기존 가져온 인기 영화 render
Jaymyong66 Mar 21, 2024
3755207
style: 영화 평점 css
Jaymyong66 Mar 21, 2024
8cf6096
chore: cypress 설정
Jaymyong66 Mar 21, 2024
fa81b18
chore: gitignore - dist,, env 추가
Jaymyong66 Mar 21, 2024
6d4fef8
chore: tsconfig 옵션 추가
Jaymyong66 Mar 21, 2024
4273068
test: 인기순 영화 페이지 E2E 테스트
Jaymyong66 Mar 21, 2024
a817135
chore: gitignore에 cypress.env.json 추가
Jaymyong66 Mar 21, 2024
a96aac3
test: TMDB에서 인기순 영화 GET요청 API 테스트
Jaymyong66 Mar 21, 2024
11532ab
test: TMDB에서 영화 검색 GET요청 API 테스트
Jaymyong66 Mar 21, 2024
02db706
test: Fixture를 이용한 인기순 영화 목록 테스트
Jaymyong66 Mar 21, 2024
75004f9
test: Fixture를 이용한 영화 검색 결과 렌더링 테스트
Jaymyong66 Mar 21, 2024
28af29a
test: fixture - search 데이터 업데이트
Jaymyong66 Mar 21, 2024
88c2cc7
chore: output Path 수정
00kang Mar 21, 2024
acda9b6
refactor: image 디렉토리 위치 변
00kang Mar 23, 2024
b27638f
refactor: css 디렉토리 위칭 이동 및 css variables 생성
00kang Mar 23, 2024
ea1b8a3
refactor: 매직넘버 상수화
00kang Mar 23, 2024
7b4d067
refactor: ErrorRender로 클래스명 수정
00kang Mar 23, 2024
2094ff8
refactor: SearchBox 컴포넌트 내 함수 이름 변경 및 메서드 분리
00kang Mar 23, 2024
83c4cd4
refactor: MovieStore와 SearchMovieStore에서 API 호출 로직 분리
00kang Mar 25, 2024
24db597
refactor: DocumentFragement 사용으로 DOM 접근 줄이기
00kang Mar 25, 2024
9cb0f27
refactor: early return으로 depth 줄이기
00kang Mar 25, 2024
e2f6ecd
refactor: #genereateMovieList와 #generateSearchMovieList의 중복 코드 메서드 분리
00kang Mar 25, 2024
5399034
refctor: 헤더 고정, 포스터 이미지 없는 데이터 이미지 삽입
00kang Mar 25, 2024
64dd479
refactor: 검색결과에 검색어(해리)가 포함되어 있는지 test 코드 추가
00kang Mar 25, 2024
065f2c6
feat: deploy 디렉토리에 css와 png 추가
00kang Mar 25, 2024
a6111ca
chore: eslint, package, tsconfig 설정
00kang Mar 25, 2024
cefc87f
docs: 구현 기능 목록 작성
00kang Mar 26, 2024
f109faa
feat: 스켈레톤의 <a> 태그 제거
00kang Mar 26, 2024
2cc746c
feat: 임의 데이터를 활용한 모달창 오픈
00kang Mar 26, 2024
1b4ea09
feat: 모달 클로즈 기능 구현
00kang Mar 27, 2024
0fbc146
feat: movieDetail API로부터 데이터 받아와서 모달에 보여주기
00kang Mar 28, 2024
1c2c72e
fix: 이미지 영역 고정
00kang Mar 28, 2024
af8d8e8
fix: 모달 내 텍스트 영역 조정
00kang Mar 28, 2024
b1e8768
fix: 모달 클로즈 오류 비동기 키워드 사용으로 해결
00kang Mar 29, 2024
28af320
feat: 클릭한 무비카드에 맞는 데이터 모달에 띄우기
00kang Mar 31, 2024
5569bdb
feat: API 요청 메서드 분리
00kang Apr 1, 2024
ccb81ff
feat: 더보기 버튼 대신 무한 스크롤 기능 구현
00kang Apr 1, 2024
afa3b94
refactor: 데이터 로드시 스켈레톤 ui 확인을 위한 딜레이 메서드 삭제
00kang Apr 1, 2024
0148e13
feat: 모달 내 별점 0으로 세팅
00kang Apr 1, 2024
7207d52
feat: 모달 내 별점 클릭 이벤트 구현
00kang Apr 1, 2024
5fb1acf
feat: 모달 내 평점 소수점 2자리까지 표현
00kang Apr 1, 2024
73a10cf
feat: tablet size 반응형 디자인 적용
00kang Apr 1, 2024
2d77149
feat: tablet size 일 때 스켈레톤 ui 6개로 변경
00kang Apr 1, 2024
c1a406b
feat: mobile size일 때 그리드 조절
00kang Apr 1, 2024
46943e3
feat: mobile size일 때 스켈레톤 4개로 조정
00kang Apr 1, 2024
15111d6
feat: mobile size일 때 모달 사이즈 수정
00kang Apr 1, 2024
2dc4077
feat: npm run build
00kang Apr 1, 2024
779cebb
feat: 스크립트 추가 후 npm run build
00kang Apr 1, 2024
485910f
Merge branch '00kang' into step2
00kang Apr 1, 2024
7e3748e
feat: npm run build
00kang Apr 1, 2024
de041e3
feat: npm run build
00kang Apr 1, 2024
dc31890
Merge branch 'deploy-step2' into step2
00kang Apr 1, 2024
8209206
feat: import문 삽입
00kang Apr 1, 2024
1c9ff19
refactor: mobile, tablet 사이즈 기준 상수화
00kang Apr 2, 2024
6c861dd
refactor: let 연산자 삭제
00kang Apr 2, 2024
4eaa732
refactor: moviesData의 타입 명확히 Movie[]로 지정
00kang Apr 2, 2024
6707341
feat: 무한 스크롤 구현!
00kang Apr 2, 2024
b64fdfc
refactor: 반응형 디자인 수정
00kang Apr 2, 2024
6864ff6
feat: 모달 내 별점 localstorage에 반영 및 localstorage에 저장된 결과 보여주기
00kang Apr 3, 2024
4dac6b6
feat: ESC key 로 모달 클로즈
00kang Apr 3, 2024
0330025
docs: 업데이트
00kang Apr 3, 2024
973037a
feat: 영화 포스터와 검색창 hover시 border 디자인
00kang Apr 3, 2024
9709602
feat: 최상단으로 이동하는 버튼
00kang Apr 3, 2024
a1a8443
refactor: 모달 디테일 수정
00kang Apr 3, 2024
9858971
fix: 모달 오픈시 무비리스트의 스크롤이 최상단으로 가는 문제 해결
00kang Apr 3, 2024
8cc5075
refactor: store 코드 메서드 분리
00kang Apr 3, 2024
06cad8e
fix: 없는 데이터에 forEach() 를 사용하는 문제
00kang Apr 3, 2024
48726d8
refactor: response 반환
00kang Apr 3, 2024
5cdec22
refactor: 메서드 분리
00kang Apr 3, 2024
4e169ca
refactor: return new Error
00kang Apr 3, 2024
bff9d97
refactor: 스크립트 파일 제거
00kang Apr 3, 2024
c846053
fix: 초기 세팅 시 별 이미지 안 뜨는 문제
00kang Apr 4, 2024
4542fb2
refactor: movieCard 클래스 메서드 분리
00kang Apr 4, 2024
722cb70
refactor: Modal 클래스 메서드 분리
00kang Apr 4, 2024
2fadb45
refactor: Modal 클래스 분리 : Modal, MovieInfo, VoteHandler
00kang Apr 4, 2024
cb164ab
feat: 모달에 skeleton UI 추가
00kang Apr 4, 2024
fa80a28
refactor: 반응형 디자인 수정 및 모달 스켈레톤 UI 적용
00kang Apr 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"consistent-return": "off",
"no-return-await": "off",
"no-plusplus": "off",

"camelcase": "off",
"object-curly-newline": "off",
},
"env": {
"es6": true,
Expand Down
87 changes: 47 additions & 40 deletions src/components/Modal.ts
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

오 이제 잘 보이네요 ㅎㅎ

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미지 닫는 태그가 소스에 딱 붙어있어서 그랬더라구요

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ import NoImage from '../images/no-image.png';
import StarFilled from '../images/star_filled.png';
import StarEmpty from '../images/star_empty.png';

interface ModalContent {
title: string;
posterPath: any;
genres: string;
voteAverage: string;
overview: string;
}
export default class Modal {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 Modal 클래스의 역할(모달 생성, 오픈, 클로즈, 영화 데이터 넣어주기, vote 영역 관리) 이 너무 방대한 것 같습니다. 이를 리팩토링한다면 모달 오픈, 클로즈 / 영화 데이터 넣어주기 + vote 관리 로 생각 중인데,
vote 관련 기능을 따로 분리해야 되는지도 궁금합니다.
+) 유조는 이렇게 코드를 분리할 때의 tip이나 기준이랄 게 있을까요?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트를 기준으로 분리를 많이 하는거 같아요.

컴포넌트 단위의 테스트를 진행할 때 Testable 한 지를 기준으로 삼아보셔도 좋을거 같아요

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

조금 더 자세히 적자면 지금 Modal이 하는 역할이 많기 때문에 Modal에 대한 테스트를 진행하기 위해서는 굉장히 많은 코드가 작성되어야 할텐데요. 말씀해주신 내용처럼 vote 영역을 따로 분리한다면 테스트를 작성할 때 vote 영역에서는 vote에 대한 동작만 테스트를 진행하면 되겠죠. 이런 식으로 컴포넌트 내에서도 여러 컴포넌트로 나뉠 수 있고 그에 따른 역할과 책임이 있을텐데 이 부분을 고려해서 분리해보시면 좋을거 같아요 ㅎㅎ

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오😯 테스트를 고려해서 나누어 본다. 그동안에는 관심사를 기반으로 분리하다고 했을 때 확 와닿지 않는 느낌이었는데 좋네요! 감사합니다.

#isOpen = false;

Expand All @@ -29,14 +36,28 @@ export default class Modal {
this.#removeExistingModal();

const movieDetail = await this.#fetchMovieDetail();
const modalContent = this.#prepareModalContent(movieDetail);

const modalElement = this.#createModalElement(movieDetail);
const modalElement = this.#createModalElement(modalContent);
document.body.appendChild(modalElement);
this.#modalElement = modalElement;

this.#setupModalEventListener(this.#modalElement);
}

#prepareModalContent(movieDetail: any) {
const { title, poster_path, genres, vote_average, overview } = movieDetail;

return {
title,
posterPath: poster_path ? `https://image.tmdb.org/t/p/w500${poster_path}` : NoImage,
genres: genres.map((genre: any) => genre.name).join(', '),

voteAverage: vote_average.toFixed(2),
overview: overview || '해당 영화의 줄거리 정보가 없습니다.',
};
}

get Element() {
return this.#modalElement;
}
Expand All @@ -56,19 +77,17 @@ export default class Modal {
}

// eslint-disable-next-line max-lines-per-function
#createModalElement(movieDetail: any) {
#createModalElement(content: ModalContent) {
const { title, posterPath, genres, voteAverage, overview } = content;

const modalElement = document.createElement('div');
modalElement.classList.add('modal', 'modal--open');

const posterPath = movieDetail.poster_path ? `https://image.tmdb.org/t/p/w500${movieDetail.poster_path}` : NoImage;
const genres = movieDetail.genres.map((genre: any) => genre.name).join(', ');
const overview = movieDetail.overview ? movieDetail.overview : '해당 영화의 줄거리 정보가 없습니다.';

const modalHTML = /* html */ `
<div class="modal-backdrop"></div>
<div class="modal-container">
<div class="modal-header">
<h3 class="detail-title text-detail-title">${movieDetail.title}</h3>
<h3 class="detail-title text-detail-title">${title}</h3>
<button class="modal-close-button"></button>
</div>
<div class="modal-body">
Expand All @@ -79,19 +98,15 @@ export default class Modal {
<p class="detail-genres text-detail-contents">${genres}</p>
<p class="detail-vote_average text-detail-contents">
<img src=${StarFilled} alt="별점" class="star-start" />
${movieDetail.vote_average.toFixed(2)}
${voteAverage}
</p>
</div>
<p class="detail-overview text-detail-contents">${overview}</p>
</div>
<div class="my-vote">
<p class="my-vote-title text-detail-vote">내 별점</p>
<div class="my-vote-body">
<button><img src=${StarEmpty} /></button>
<button><img src=${StarEmpty} /></button>
<button><img src=${StarEmpty} /></button>
<button><img src=${StarEmpty} /></button>
<button><img src=${StarEmpty} /></button>
${Array(5).fill(`<button><img src=${StarEmpty} /></button>`).join('')}
</div>
<p class="my-vote-number text-detail-vote-contents">0</p>
<p class="my-vote-description text-detail-vote-contents">남겨주세요</p>
Expand All @@ -118,48 +133,35 @@ export default class Modal {
});
}

// eslint-disable-next-line max-lines-per-function
async openModal() {
if (this.#isOpen) return;

document.body.style.overflow = 'hidden';
await this.generateModal();

this.#VoteHandler();

document.addEventListener('keydown', this.#handleKeyDown);

this.#isOpen = true;
}

// eslint-disable-next-line max-lines-per-function
#VoteHandler() {
const savedVotes = localStorage.getItem('myVoteResult');
if (savedVotes) {
const savedVotesJSON = JSON.parse(savedVotes);
this.#myVoteResult = savedVotesJSON;
}
const voteForMovie = savedVotes ? JSON.parse(savedVotes)[this.#movieId] : null;

const voteForMovie = this.#myVoteResult[this.#movieId];
if (voteForMovie) {
this.#updateVoteStar(voteForMovie);
this.#updateVoteText(voteForMovie);
}
}

// eslint-disable-next-line max-lines-per-function
#updateVoteStar(voteForMovie: number) {
const starButtons = this.#modalElement?.querySelectorAll('.my-vote-body button img');

if (starButtons) {
starButtons.forEach((starButton, index) => {
if (index < voteForMovie / 2) {
starButton.setAttribute('src', StarFilled);
} else {
starButton.setAttribute('src', StarEmpty);
}
});
}
if (!starButtons) return;

starButtons.forEach((starButton, index) => {
starButton.setAttribute('src', index < voteForMovie / 2 ? StarFilled : StarEmpty);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드가 훨씬 깔끔해졌네요 👍 👍 👍

}

#updateVoteText(voteForMovie: number) {
Expand Down Expand Up @@ -204,7 +206,6 @@ export default class Modal {
}
}

// eslint-disable-next-line max-lines-per-function
handleStarClick(starIndex: number) {
const myVoteNumber = this.#modalElement?.querySelector('.my-vote-number');
const myVoteDescription = this.#modalElement?.querySelector('.my-vote-description');
Expand All @@ -214,17 +215,23 @@ export default class Modal {
const starButtons = this.#modalElement?.querySelectorAll('.my-vote-body button img');
if (!starButtons) return;

this.updateStarButtons(starButtons, starIndex);
this.updateVoteInfo(myVoteNumber, myVoteDescription, starIndex);
}

updateStarButtons(starButtons: NodeListOf<Element>, starIndex: number) {
starButtons.forEach((starButton, index) => {
if (index <= starIndex) {
starButton.setAttribute('src', StarFilled);
} else {
starButton.setAttribute('src', StarEmpty);
}
starButton.setAttribute('src', index <= starIndex ? StarFilled : StarEmpty);
});
}

updateVoteInfo(myVoteNumber: Element, myVoteDescription: Element, starIndex: number) {
const newMyVoteNumber = myVoteNumber as HTMLElement;
const newMyVoteDescription = myVoteDescription as HTMLElement;

const myVoteKey = (starIndex + 1) * 2;
myVoteNumber.textContent = myVoteKey.toString();
myVoteDescription.textContent = VOTE[myVoteKey];
newMyVoteNumber.textContent = myVoteKey.toString();
newMyVoteDescription.textContent = VOTE[myVoteKey];

this.#myVoteResult[this.#movieId] = myVoteKey;
localStorage.setItem('myVoteResult', JSON.stringify(this.#myVoteResult));
Expand Down