React 부트캠프 TIL 11

정다롱·2024년 8월 7일

내일배움캠프 TIL

목록 보기
10/39

🖥️ 팀프로젝트 영화 소개 사이트 만들기

- 필수 구현 사항

  • TMDB 또는 영화진흥위원회 오픈 API 이용(택 1 또는 중복 사용)
  • 영화정보 상세 페이지 구현
  • 상세 페이지 영화 리뷰 작성 기능 구현
  • 작성한 리뷰 데이터는 localStorage 적재
  • github PR(=Pull Request) 사용한 협업
  • UX를 고려한 validation check (유효성 검사)
  • 하기 기재된 Javascript 문법 요소를 이용하여 구현

- 선택 구현 사항

  • 반응형 UI 구성하기
  • 상세페이지 리뷰 수정 및 삭제 기능 구현
  • 조건에 맞는 카드 리스트 정렬 기능(이름순, 별점순 등 자유롭게)
  • 추가 기능 아무거나!

⭐ 사용 스킬

  • HTML
  • CSS
  • Vanilla javascript

✨ 필수 구현 과제 목록

⭕ github PR(=Pull Request) 사용한 협업

  • 깃은 솔직히 잘 사용했다고 하고 싶지만 도저히 빈말로도 잘 썼습니다! 라는 말이 나오지 않는다. 무수히 많은 push, pull 오류... 브랜치 이동 과정에서 스크립트가 날아가는 아찔한 순간도 있었지만 어찌저찌 잘 이겨내 완성하긴 했다. 먼저 개발을 하셨던 분들이 자기는 개발보다 git 공부가 더 어렵다. 라고 하는 말이 그냥 장난인 줄 알았는데 진짜일지도... 그래도 브랜치를 분리하고 병합하는 과정에서 생기는 자잘한 오류들을 해결하며 깃에 대한 이해도가 조금 올라간 것 같긴 하다. 다음 협업때는 오류를 최소화를 위해 파이팅!!

⭕ 영화정보 상세 페이지 구현

// 카드를 뿌리는 부분에서 클릭하면 링크 뒤에 영화 ID값 추가하여 이동
 window.location.href = `detailpage.html?id=${movie.id}`;
window.onload = async function () {
  try {
    // URL 쿼리파라미터에서 영화 ID를 가져옴
    const urlParameter = new URLSearchParams(window.location.search);
    // 쿼리파라미터에서 id의 값을 가져옴
    const selectedMovieId = urlParameter.get("id");
    // 카드를 선택하면 상세 정보를 가져와서 표시
    if (selectedMovieId) {
      const movieDetails = await fetchMovieDetails(selectedMovieId);
      printDetail(movieDetails);
    }
  } catch (error) {
    console.error("에러 발생", error);
  }
};
// 상세 페이지에 데이터를 프린트하는 함수
function printDetail(movieDetails) {
  const detaillWrap = document.querySelector(".detaillWrap");
 // 출연배우의 profile_path있으면 프로필 링크 사용
 // 없으면 성별에 따라 tmdb null 이미지 사용
  const actorList = movieDetails.cast
    .map((actor) => {
      let profileImage;
      if (actor.profile_path) {
        profileImage = `https://image.tmdb.org/t/p/w500${actor.profile_path}`;
      } else {
        if (actor.gender === 0) {
          profileImage = `https://www.themoviedb.org/assets/2/v4/glyphicons/basic/glyphicons-basic-4-user-grey-d8fe957375e70239d6abdd549fd7568c89281b2179b5f4470e2e12895792dfa5.svg`;
        } else {
          profileImage = `https://www.themoviedb.org/assets/2/v4/glyphicons/basic/glyphicons-basic-36-user-female-grey-d9222f16ec16a33ed5e2c9bbdca07a4c48db14008bbebbabced8f8ed1fa2ad59.svg`;
        }
      }

TMDB API 자체에 사진 등록이 안 된 배우가 있어서 프로필 사진이 없는 경우 성별에 따라 임의로 이미지를 삽입했다. 내가 담당한 부분은 아니지만 매우 까다로울 것 같았는데 너무너무 잘해주셔서 병합할 때도 편했고 오류도 가장 적었던 것 같다!!

⭕ 로컬 스토리지를 사용한 리뷰 기능

  • 15일차 TIL 작성

⭕ UX를 고려한 validation check (유효성 검사)

function editComment(event) {
  const editTime = event.target.getAttribute("data-time");
  let comments = getComments(movieId);
  const comment = comments.find((comment) => comment.time === editTime);

  if (comment) {
 	 // 프롬포트 창으로 비밀번호 입력 받기
    const password = prompt("비밀번호 확인");
    // 일치하는 경우 댓글 수정 팝업 띄워 수정한 내용으로 다시 저장
    if (password === comment.password) {
      const newComment = prompt("새로운 댓글 내용을 입력하세요", comment.comment);
      if (newComment) {
        comment.comment = newComment;
        localStorage.setItem(`${movieId}_comment`, JSON.stringify(comments));
        loadComments(movieId);
      }
    } else {
    	// 일치하지 않는 경우 불일치 안내 팝업
      alert("비밀번호가 일치하지 않습니다");
    }
  }
}

function deleteComment(event) {
  const deleteTime = event.target.getAttribute("data-time");
  let comments = getComments(movieId);
  const commentIndex = comments.findIndex((comment) => comment.time === deleteTime);
	
    // 댓글이 존재하는지 확인하고 존재하면 비밀번호 입력 받기
  if (commentIndex !== -1) {
    const password = prompt("비밀번호 확인");
    if (password === comments[commentIndex].password) {
    // 일치하면 배열에서 해당 댓글 삭제 후 코멘트 목록 다시 저장
      comments.splice(commentIndex, 1);
      localStorage.setItem(`${movieId}_comment`, JSON.stringify(comments));
      loadComments(movieId);
    } else {
    // 일치하지 않는 경우 불일치 안내 팝업
      alert("비밀번호가 일치하지 않습니다");
    }
  }
}

⭕ Javascript 문법 요소를 이용하여 구현

  • 기능을 구현하다 보니 자연스럽게 필수 문법을 모두 사용하게 되었다...



✨ 선택 구현 과제 목록

⭕ 반응형 UI 구성하기

// 슬라이드에 쓸 변수들
function slider(containerId, box) {
  function getWidth() {
    const vwInPx = (100 / 100) * window.innerWidth; // 100vw 픽셀로 변환
    const slideWidth = vwInPx - 140; //140px 빼기
    return slideWidth;
  }

  let slides = document.getElementById(`${containerId}`);
  let slide = document.querySelectorAll(`#${containerId} .moviePoster`);
  let NextBtn = document.querySelector(`#${box} .slideBtn .next`);
  let PrevBtn = document.querySelector(`#${box} .slideBtn .prev`);
  // 카드 한 개 넓이
  let cardWidth = 285;
  // 디스플레이 넓이
  let display = getWidth();
  // 한 화면에 들어가는 카드 갯수
  let onedisCard = display / cardWidth;
  // 총 카드 갯수
  let totalCard = slide.length;
  // 클릭 횟수 계산
  let clickSlide = Math.floor(totalCard / onedisCard);
  let spareCard = totalCard % onedisCard;
  // 카드 남는지 안 남는지 계산
  let totalClick = spareCard === 0 ? clickSlide : clickSlide + 1;
  // 나머지 카드 넓이
  let spareCardWidth = spareCard * cardWidth;
  // 슬라이드 이동 넓이
  let moveWidth = onedisCard * cardWidth - 200;

  // console.log(`디스플레이 넓이: ${display}px`);
  // console.log(`한 화면에 들어가는 카드 갯수: ${onedisCard}`);
  // console.log(`총 필요한 클릭 횟수: ${totalClick}`);
  // console.log(`나머지 카드: ${spareCard}`);
  // console.log(`슬라이드 이동 넓이: ${moveWidth}`);
  // console.log(`마지막 이동 넓이: ${spareCardWidth}`);

  let clickCount = 0;

  NextBtn.addEventListener("click", function () {
    moveSlide(clickCount + 1);
  });
  PrevBtn.addEventListener("click", function () {
    moveSlide(clickCount - 1);
  });

  function moveSlide(click) {
    if (spareCard == 0) {
      slides.style.left = -1 * click * moveWidth + "px";
      clickCount = click;
    } else {
      slides.style.left = -1 * click * moveWidth + "px";
      clickCount = click;
      if (click === totalClick - 1) {
        slides.style.left = -1 * (click - 1) * moveWidth - spareCardWidth - 200 * (click - 1) + "px";
      }
    }

    if (clickCount === totalClick || clickCount < 0) {
      slides.style.left = 0;
      clickCount = 0;
    }
    // console.log(slides.style.left, clickCount);
  }
}

페이지 자체를 반응형으로 작업하지는 못했지만 슬라이드 부분에 있어서 사용자의 디스플레이 넓이에 따라 클릭 횟수, 슬라이드 이동 넓이를 유동적으로 조절하게 구현했다. 슬라이드 작업이 제일 오래걸렸지만 너무너무너무 재미있었다..

⭕ 상세페이지 리뷰 수정 및 삭제 기능 구현

  • 상위 유효성 검사 부분에서 확인 가능!

⭕ 조건에 맞는 카드 리스트 정렬 기능(이름순, 별점순 등 자유롭게)

function baseData() {
  return Promise.all([
    fetchMovies(upcomingUrl, "upcomingContainer", "upBox"),
    fetchMovies(nowUrl, "nowContainer", "nowBox"),
    fetchMovies(topUrl, "topContainer", "topBox"),
    fetchMovies(popularUrl, "popularContainer", "popularBox")
  ])
    .then(() => {
      console.log("영화 title, overview 등이 성공적으로 로드되었습니다.");
    })
    .catch((error) => {
      console.error("영화 title, overview 로드 중 오류 발생:", error);
    });
}

우리는 정렬 기능을 사용하지 않고 애초에 인기 영화, 최고 평점 영화, 상영중인 영화, 개봉 예정 영화를 따로 받아와 슬라이드 기능을 통해 삽입했다. 여러개의 API를 fetch로 받아오기 위해 Promise.all을 사용했다!



팀 프로젝트를 마치며

할 게 너무 많았고 어렵고 힘들었지만 팀원분들과 함께 으쌰으쌰 좋은 분위기로 끝낼 수 있었던 것 같다. 9시가 지나고서도 다들 zep에 남아서 노래 들으면서 같이 웃고 또 공부할때는 열심히 집중하고!! 특히 이번 프로젝트를 하면서 나 스스로가 오.. 나 좀 늘고 있는 것 같은데? 라는 생각이 들어서 좋았던 것 같다. 15조 당무땡 짱❤️

완성 페이지

0개의 댓글