[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <post.js>
2024. 12. 20. 11:01ㆍ웹개발 포트폴리오
728x90
반응형
import React, { useState, useEffect, useCallback } from "react";
import styled from "styled-components";
import { auth } from "../firebase";
import { updateDoc, doc, getDoc, deleteDoc } from "firebase/firestore";
import { getDownloadURL, ref } from "firebase/storage";
import { db, storage } from "../firebase";
import { onAuthStateChanged } from "firebase/auth";
// 스타일 컴포넌트 정의
const Wrapper = styled.div`
display: flex;
flex-direction: column;
text-align: center;
height: 100vh;
`;
const Column = styled.div`
display: flex;
flex-direction: column;
height: 100%;
`;
const Username = styled.p`
font-weight: 600;
color: #333;
`;
const DateSpan = styled.span`
display: block;
color: #333;
width: 100%;
margin-top: 15px;
margin-bottom: 15px;
padding: 0;
box-sizing: border-box;
`;
const PhotoGrid = styled.div`
gap: 10px;
flex-wrap: wrap;
`;
const Photo = styled.img`
width: 80px;
height: 80px;
border-radius: 50%;
object-fit: cover;
`;
const Payload = styled.div`
margin: 10px 0px;
color: #333;
width: 100%;
word-break: break-word;
white-space: pre-wrap;
height: 80vh;
overflow-y: auto;
overflow-x: hidden;
@media (max-width: 600px) {
font-size: 1.1rem;
}
`;
const LikeButton = styled.button`
font-size: 1vw;
background-color: ${(props) => (props.disabled ? "#ccc" : "#ff5a5f")};
color: white;
border: 0;
padding: 5px 10px;
border-radius: 5px;
cursor: ${(props) => (props.disabled ? "not-allowed" : "pointer")};
margin-top: 10px;
@media (max-width: 600px) {
font-size: 0.8rem;
}
`;
const LikesCount = styled.span`
margin-left: 10px;
font-size: 1vw;
color: white;
@media (max-width: 600px) {
font-size: 0.8rem;
}
`;
const Items = styled.div`
flex-direction: column;
text-align: center;
width: 100%;
justify-content: center;
align-items: center;
display: flex;
position: absolute;
bottom: 8vh;
left: 50%;
> * {
margin-bottom: 10px;
}
> *:last-child {
margin-bottom: 0;
}
transform: translateX(-50%);
`;
const DeleteButton = styled.button`
background-color: #f5957f;
color: white;
border: 0;
padding: 5px 10px;
border-radius: 5px;
cursor: pointer;
margin-top: 10px;
margin-bottom: 10px !important;
`;
// Post 컴포넌트 정의
const Post = ({ userId, id, post, photos }) => {
const [photoURL, setPhotoURL] = useState("/guest.png");
const [username, setUsername] = useState("");
const [likes, setLikes] = useState(0);
const [createdAt, setCreatedAt] = useState(null);
const [currentUserId, setCurrentUserId] = useState(null);
const [setNotificationVisible] = useState(false);
// 프로필 데이터 가져오기
const fetchProfile = useCallback(async () => {
try {
if (!userId) return;
const userDoc = await getDoc(doc(db, "users", userId));
if (userDoc.exists()) {
const userData = userDoc.data();
setUsername(userData.displayName || "익명");
const avatarUrl = await getDownloadURL(ref(storage, userData.photoURL)).catch(() => "/guest.png");
setPhotoURL(avatarUrl);
}
} catch (error) {
console.error("프로필을 불러오는 중 오류 발생:", error);
}
}, [userId]);
// 좋아요 데이터 가져오기
const fetchLikes = useCallback(async () => {
try {
const postDoc = await getDoc(doc(db, "posts", id));
if (postDoc.exists()) {
const postData = postDoc.data();
setLikes(postData.likes || 0);
setCreatedAt(postData.createdAt ? postData.createdAt.toDate() : null);
}
} catch (error) {
console.error("좋아요 데이터를 불러오는 중 오류 발생:", error);
}
}, [id]);
// 유효성 체크 후 함수 실행
useEffect(() => {
fetchProfile(); // 업데이트 될 때만 실행
}, [fetchProfile]);
useEffect(() => {
fetchLikes(); // 업데이트 될 때만 실행
}, [fetchLikes]);
useEffect(() => {
const unsubscribe = onAuthStateChanged(auth, (user) => {
if (user) {
setCurrentUserId(user.uid); // 상태 업데이트
} else {
setCurrentUserId(null);
}
});
return () => unsubscribe();
}, []);
// 좋아요 처리 함수
const handleLike = async () => {
if (currentUserId === userId) return;
const docRef = doc(db, "posts", id);
try {
const docSnap = await getDoc(docRef);
if (docSnap.exists()) {
const postData = docSnap.data();
const likedUsers = postData.likedUsers || [];
if (likedUsers.includes(currentUserId)) return;
const newLikes = (postData.likes || 0) + 1;
const updatedLikedUsers = [...likedUsers, currentUserId];
await updateDoc(docRef, {
likes: newLikes,
likedUsers: updatedLikedUsers,
});
setLikes(newLikes);
setNotificationVisible(true);
setTimeout(() => setNotificationVisible(false), 2000);
}
} catch (e) {
console.error("좋아요 업데이트 중 오류 발생:", e);
}
};
// 삭제 처리 함수
const onDeletePost = async () => {
const confirmDelete = window.confirm("정말 이 포스트를 삭제하시겠습니까?");
if (!confirmDelete) return;
try {
const docRef = doc(db, "posts", id);
await deleteDoc(docRef);
console.log("포스트가 삭제되었습니다.");
} catch (error) {
console.error("삭제 중 오류 발생:", error);
}
};
return (
<Wrapper>
<Column>
<PhotoGrid>
<Photo src={photoURL} alt="Posted Image" />
</PhotoGrid>
<Username>{username}</Username>
<Payload dangerouslySetInnerHTML={{ __html: post }} />
<Items>
<LikeButton onClick={handleLike} disabled={currentUserId === userId}>
함께하고 싶어요!
<LikesCount>{likes}</LikesCount>
</LikeButton>
{auth.currentUser?.uid === userId && (
<DeleteButton onClick={onDeletePost}>지우기</DeleteButton>
)}
<DateSpan>{createdAt ? createdAt.toLocaleString() : "알 수 없는 시간"}</DateSpan>
</Items>
</Column>
</Wrapper>
);
};
export default Post;
728x90
반응형
'웹개발 포트폴리오' 카테고리의 다른 글
[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <timeline.js> (0) | 2024.12.20 |
---|---|
[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <posting.js> (0) | 2024.12.20 |
[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <profile.js> (0) | 2024.12.20 |
[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <loading-screen.js> (0) | 2024.12.20 |
[개인 포트폴리오] [클라이밍 커뮤니티 SNS] 2. 코드 분석 <github-btn.js> (0) | 2024.12.20 |