[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <timeline.js>

2024. 12. 20. 11:10웹개발 포트폴리오

728x90
반응형
import styled from "styled-components";
import {
  collection,
  getDoc,
  limit,
  onSnapshot,
  orderBy,
  query,
} from "firebase/firestore";
import React, { useState, useEffect } from "react";
import Modal from "react-modal";
import Post from "./post";
import { auth, db } from "../firebase";

Modal.setAppElement("#root");

const Wrapper = styled.div`

  @media (max-width: 600px) {
    flex-direction: column; /* 세로 방향 배치 */
    padding: 10px; /* 여백 축소 */
  }

  display: flex;
  flex-direction: column;
  width: 100%;
  overflow-y: auto;
  margin-left: 20vw;
`;

const PostGrid = styled.div`
  display: grid;
  grid-template-columns: repeat(3, 1fr); /* 3열 그리드 */
  gap: 20px;
  width: 80vw; /* 전체 페이지의 80% 너비를 차지 */
  padding: 20px;
  box-sizing: border-box;
    @media (max-width: 600px) {
      grid-template-columns: repeat(1, 1fr); /* 3열 그리드 */
    }
`;

const PostCard = styled.div`
  background-color: #fff;
  border-radius: 10px;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  padding: 20px;
  display: flex;
  flex-direction: column;
  text-align: center;
  height: 110vh;
  position: relative;
    @media (max-width: 600px) {
      height: 70vh;
    }
`;

const PostThumbnail = styled.img`
  width: 100%;
  height: 150px;
  object-fit: cover;
  border-radius: 8px;
  margin-bottom: 10px;
  cursor: pointer;
    @media (max-width: 600px) {
      height: 200px;
    }
`;

const ProfileContainer = styled.div`
  display: flex;
  flex-direction: column;
  gap: 10px;
  margin-top: 10px;
`;

const CloseButton = styled.button`
  background-color: none;
  color: #8f89a1;
  border: none;
  cursor: pointer;
  position: absolute;
  top: 10px;
  right: 10px;
`;

const ModalImage = styled.img`
  width: 100vw;
  height: 70vh;
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
`;

const ModalNavigation = styled.div`
  display: flex;
  justify-content: space-between;
  margin-top: 10px;
`;

const ModalButton = styled.button`
  background-color: #333;
  color: white;
  border: none;
  padding: 10px;
  border-radius: 5px;
  cursor: pointer;
`;


const Timeline = () => {
  const [posts, setPosts] = useState([]);
  const [, setCurrentUserId] = useState(null);
  // 상태 값을 사용하지 않고 업데이트 함수(setCurrentUserId)만 사용
  const [modalIsOpen, setIsOpen] = useState(false);
  const [selectedPhotos, setSelectedPhotos] = useState([]);
  const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);

  useEffect(() => {
    const unsubscribeAuth = auth.onAuthStateChanged((user) => {
      if (user) {
        setCurrentUserId(user.uid);
      } else {
        setCurrentUserId(null);
      }
    });

    const fetchPosts = () => {
      const postsQuery = query(
        collection(db, "posts"),
        orderBy("createdAt", "desc"),
        limit(25)
      );


      const unsubscribe = onSnapshot(postsQuery, async (snapshot) => {
      // onSnapshot은 Firestore에서 실시간 데이터를 수신하기 위한 리스너(listener)를 설정하는 메서드
        const postsData = await Promise.all(
          snapshot.docs.map(async (doc) => {
            const data = doc.data();
            const userId = data.userId;

            // 기본값 설정
            let displayName = "익명";
            let profileImage = "/guest.png";
            let photos = Array.isArray(data.photos) && data.photos.length > 0 ? data.photos : ["/empty.jpg"];

            try {
              // users 컬렉션에서 userId 기반으로 displayName과 photoURL 가져오기
              const userDoc = await getDoc(doc(db, "users", userId));
              if (userDoc.exists()) {
                const userData = userDoc.data();
                displayName = userData.displayName || "익명";
                profileImage = userData.photoURL || "/guest.png";
              }
            } catch (error) {
              console.error("Error loading user profile:", error);
            }

            return {
              id: doc.id,
              createdAt: data.createdAt ? data.createdAt.toDate() : null,
              likes: data.likes || 0,
              post: data.post || "",
              userId,
              username: displayName,
              profileImage, // users 컬렉션의 photoURL로 설정한 프로필 이미지
              photos,
            };
          })
        );
        setPosts(postsData);
      });
      return unsubscribe; // Firestore 구독 해제를 위한 unsubscribe 반환
    };

    const unsubscribePosts = fetchPosts(); // fetchPosts의 반환 값 저장

    return () => {
      unsubscribeAuth(); // Firebase Auth 구독 해제
      if (unsubscribePosts) unsubscribePosts(); // Firestore 구독 해제
    };
  }, []);

//사진 확대 보기
  const openModal = (photos) => {
    setSelectedPhotos(photos);
    setCurrentPhotoIndex(0);
    setIsOpen(true);
  };

  const closeModal = () => {
    setIsOpen(false);
    setSelectedPhotos([]);
    setCurrentPhotoIndex(0);
  };

  const showNextPhoto = () => {
    setCurrentPhotoIndex((prevIndex) =>
      prevIndex === selectedPhotos.length - 1 ? 0 : prevIndex + 1
    );
  };

  const showPreviousPhoto = () => {
    setCurrentPhotoIndex((prevIndex) =>
      prevIndex === 0 ? selectedPhotos.length - 1 : prevIndex - 1
    );
  };

  return (
    <Wrapper>
      <PostGrid>
      {posts.map((post) => (
        <PostCard key={post.id}>
          {/* 첫 번째 사진만 썸네일로 표시 */}
          {post.photos.length > 0 && (
            <PostThumbnail
              src={post.photos[0]} // 첫 번째 사진만 표시
              alt="Post Thumbnail" // 접근성을 위한 alt 텍스트
              onClick={() => openModal(post.photos)} // 전체 사진 배열 전달
            />
          )}
            <ProfileContainer>
              <Post
                key={post.id}
                id={post.id}
                userId={post.userId}
                post={post.post}
                photos={post.photos}
                profileImage={auth.currentUser.photoURL} // 프로필 이미지 전달
                username={post.username}
              />
            </ProfileContainer>
          </PostCard>
        ))}
      </PostGrid>
      <Modal isOpen={modalIsOpen} onRequestClose={closeModal} style={{ width: "100vw", height: "100vh", overlay: { zIndex: 1000 }, display: "flex", // 플렉스 박스 활성화
      justifyContent: "center", alignItems: "center"}} >
        <CloseButton onClick={closeModal}>X</CloseButton>
        {selectedPhotos.length > 0 && (
          <>
            <ModalImage src={selectedPhotos[currentPhotoIndex]} />
            <ModalNavigation>
              <ModalButton onClick={showPreviousPhoto}>이전</ModalButton>
              <ModalButton onClick={showNextPhoto}>다음</ModalButton>
            </ModalNavigation>
          </>
        )}
      </Modal>

    </Wrapper>
  );
};


export default Timeline;
728x90
반응형