React.js/SNS Project
Day 5
Jinny96
2022. 8. 24. 21:01
1. 폴더 구조
2. MyFeed 페이지
MyFeedHeader.jsx와 MyFeedMain.jsx를 MyFeed.jsx로 import하여 사용한다.
-/MyFeed/MyFeedMain.jsx
import { useEffect, useState } from "react";
import { getDocs, query, collection, where } from "firebase/firestore";
import { Grid } from "@mui/material";
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import CardMedia from '@mui/material/CardMedia';
import CardContent from '@mui/material/CardContent';
import CardActions from '@mui/material/CardActions';
import Avatar from '@mui/material/Avatar';
import IconButton from '@mui/material/IconButton';
import FavoriteIcon from '@mui/icons-material/Favorite';
import MoreVertIcon from '@mui/icons-material/MoreVert';
import { db } from "../../Env/Firebase";
import Loader from "../../Env/Loader";
const MyFeedMain = () => {
const id = sessionStorage.getItem("user_id");
const [isLoading, setIsLoading] = useState(true);
const [feeds, setFeeds] = useState([]);
useEffect(() => {
let feedsArr = [];
const q = query(collection(db, "feeds"), where("id", "==", id));
getDocs(q).then((querySnapshot) => {
querySnapshot.forEach((doc) => {
feedsArr.push(doc.data());
});
}).then(() => {
setFeeds(feedsArr);
setIsLoading(false);
});
}, []);
if (isLoading) return <Loader />
return (
<Grid container spacing={3} style={{ alignItems: "center", width: "80%", margin: "auto", marginTop: 50 }}>
{feeds ? feeds.map((item, idx) => {
return(
<Grid key={idx} item xs={12} sm={6} md={4}>
<Card sx={{ maxWidth: 345 }}>
<CardHeader
avatar={
<Avatar src={sessionStorage.getItem("user_image")} />
}
action={
<IconButton>
<MoreVertIcon />
</IconButton>
}
title={sessionStorage.getItem("user_name")}
subheader={item.time_stamp.toDate().toDateString()}
/>
<CardMedia
component="img"
height="300"
image={item.image}
alt={item.name}
style={{width: "100%"}}
/>
<CardContent>
{item.content}
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
</IconButton>
</CardActions>
</Card>
</Grid>
)
}) : <div>게시물 없음</div>}
</Grid>
)
}
export default MyFeedMain;
useEffect를 사용해 Firebase Firestorage에 get 요청을 보낸다. (feeds 컬렉션의 문서 중 id 필드 값이 로그인한 유저의 id 필드 값이 일치하는 것을 가져온다.) 이때 비동기 통신 중에는 유저에게 로딩 화면을 띄운다.
이런 포토카드 형식으로 나온다.
-MyFeed/MyFeed.jsx
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import { Grid, Typography, Button } from "@mui/material";
import { getDocs, query, collection, where } from "firebase/firestore";
import { db } from "../../Env/Firebase";
import EditHeader from "./MyFeedHeader";
import MyFeedMain from "./MyFeedMain";
const MyFeed = () => {
const navigate = useNavigate();
const id = sessionStorage.getItem("user_id");
const [data, setData] = useState();
useEffect(() => {
const intro = sessionStorage.getItem("user_introduce");
if (intro) {
setData({
name: sessionStorage.getItem("user_name"),
userImage: sessionStorage.getItem("user_image"),
countFeed: sessionStorage.getItem("user_count_feed"),
introduce: sessionStorage.getItem("user_introduce")
})
} else {
const q = query(collection(db, "users"), where("id", "==", id));
getDocs(q).then((querySnapshot) => {
querySnapshot.forEach((doc) => {
setData({
name: doc.data().name,
userImage: doc.data().image,
countFeed: doc.data().count_feed,
introduce: doc.data().introduce
})
sessionStorage.setItem("user_name", doc.data().name);
sessionStorage.setItem("user_count_feed", doc.data().count_feed);
sessionStorage.setItem("user_introduce", doc.data().introduce);
});
});
}
}, []);
/** 프로필 편집 버튼 누를 시 state안의 자료형 보내면서 "/edit"으로 이동 */
const onClickEdit = () => {
navigate("/edit", {
state: {
name: data.name,
introduce: data.introduce,
userImage: data.userImage
}
});
}
return (
<>
<EditHeader />
<Grid container style={{ alignItems: "center", textAlign: "center", width: "80%", margin: "auto", marginTop: 50 }}>
<Grid item xs={6}>
{data && <img src={data.userImage} style={{ width: "50%", height: 250, borderRadius: "50%" }} />}
</Grid>
<Grid item xs={6}>
{data && data.countFeed}
<Typography>
게시물
</Typography>
</Grid>
<Grid item xs={6} style={{ marginTop: 10 }}>
<Typography>
{data && data.name}
</Typography>
</Grid>
<Grid item xs={6}>
</Grid>
<Grid item xs={6} style={{ marginTop: 10 }}>
<Typography>
{data && data.introduce}
</Typography>
</Grid>
<Grid item xs={12} style={{ marginTop: 30 }}>
<Button variant="contained" onClick={onClickEdit} style={{ width: "60%", backgroundColor: "black", color: "white" }}>
프로필 편집
</Button>
</Grid>
</Grid>
<MyFeedMain />
</>
)
}
export default MyFeed;
최종적인 MyFeed 페이지이다. 추후에 하트를 눌렀을 때 좋아요 기능과 포토카드의 오른쪽 점을 눌러 삭제하는 기능까지 구현할 것이다.
3. CreateFeed 페이지
-/CreateFeed/CreateFeed.jsx
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { Grid, TextField, Button } from "@mui/material";
import { addDoc, collection, query, where, getDocs, updateDoc, doc, serverTimestamp } from "firebase/firestore";
import { ref, uploadBytesResumable, getDownloadURL } from "firebase/storage";
import Swal from 'sweetalert2';
import CreateFeedHeader from "./CreateFeedHeader";
import Loader from "../../Env/Loader";
import { db, storage } from "../../Env/Firebase";
const CreateFeed = () => {
const navigate = useNavigate();
const [isLoading, setIsLoading] = useState(false);
const [file, setFile] = useState();
const [image, setImage] = useState("")
const [content, setContent] = useState("");
const onChangeImage = (e) => {
let file = e.target.files[0];
let reader = new FileReader();
reader.onloadend = (e) => {
setFile(file);
setImage(reader.result);
}
if (file) reader.readAsDataURL(file);
}
const onChange = (event) => {
const { target: { id, value } } = event;
if (id === "content") {
setContent(value);
}
}
const onClickComplete = () => {
setIsLoading(true);
const storageRef = ref(storage, `/user_feeds/${file.name}`);
uploadBytesResumable(storageRef, file).then(() => {
getDownloadURL(storageRef).then((url) => {
addDoc(collection(db, "feeds"), {
id: sessionStorage.getItem("user_id"),
name: sessionStorage.getItem("user_name"),
image: url,
content: content,
time_stamp: serverTimestamp(),
user_image: sessionStorage.getItem("user_image")
});
const q = query(collection(db, "users"), where("id", "==", sessionStorage.getItem("user_id")));
getDocs(q).then(querySnapshot => {
querySnapshot.forEach((document) => {
const userRef = doc(db, "users", document.id);
updateDoc(userRef, {
count_feed: document.data().count_feed + 1
});
sessionStorage.setItem("user_count_feed", document.data().count_feed + 1);
setIsLoading(false);
Swal.fire({
icon: 'success',
title: '업로드 완료',
html: '업로드가 정상적으로 완료되었습니다.',
showClass: {
popup: 'animate__animated animate__fadeInDown'
},
hideClass: {
popup: 'animate__animated animate__fadeOutUp'
}
}).then(() => {
navigate("/home");
});
});
});
});
});
}
if (isLoading) return <Loader />
return (
<>
<CreateFeedHeader />
<Grid container style={{ alignItems: "center", textAlign: "center", width: "50%", margin: "auto", marginTop: 70, height: "auto" }}>
<Grid item xs={12}>
{image ? <img src={image} style={{ width: "50%", height: 300 }} />
:
<img src="/img/default.png" style={{ width: "50%", height: 300 }} />
}
</Grid>
<Grid item xs={12}>
<input
type="file"
id="contained-button-file"
style={{ display: 'none' }}
onChange={onChangeImage}
/>
<label htmlFor="contained-button-file">
<Button color="primary" component="span">
사진 업로드
</Button>
</label>
</Grid>
<Grid item xs={12} style={{ marginTop: 20 }}>
<TextField
id="content"
label="Content"
multiline
rows={5}
sx={{
"& .MuiInputLabel-root": { color: 'black' },
"& .MuiOutlinedInput-root": { "& > fieldset": { borderColor: "black" } },
"& .MuiOutlinedInput-root.Mui-focused": { "& > fieldset": { borderColor: "black" } },
"& .MuiOutlinedInput-root:hover": { "& > fieldset": { borderColor: "black" } },
width: "50%"
}}
value={content}
onChange={onChange}
/>
</Grid>
<Grid item xs={12} style={{ marginTop: 30 }}>
<Button variant="contained" onClick={onClickComplete} style={{ width: "50%", backgroundColor: "black", color: "white" }}>
완료
</Button>
</Grid>
</Grid>
</>
)
}
export default CreateFeed;
내 피드를 생성하는 페이지다. 올릴 사진과 내용을 적고 완료 버튼을 누르면 Firebase Storage와 Firestore에 저장된다.
헤더는 생략하겠다.
4. Home 페이지
-/Home/Home.jsx
import { useEffect, useState } from "react";
import { Grid } from "@mui/material";
import Card from '@mui/material/Card';
import CardHeader from '@mui/material/CardHeader';
import CardMedia from '@mui/material/CardMedia';
import CardContent from '@mui/material/CardContent';
import CardActions from '@mui/material/CardActions';
import Avatar from '@mui/material/Avatar';
import IconButton from '@mui/material/IconButton';
import FavoriteIcon from '@mui/icons-material/Favorite';
import { getDocs, collection } from "firebase/firestore";
import { db } from "../../Env/Firebase";
import Header from "./Header";
const Home = () => {
const [data, setData] = useState([]);
useEffect(() => {
const uid = sessionStorage.getItem("user_id");
if (!uid) {
alert("로그인 후 이용해주세요.");
window.location.replace("/");
}
}, []);
useEffect(() => {
let dataArr = [];
getDocs(collection(db, "feeds")).then((querySnapshot) => {
querySnapshot.forEach((doc) => {
dataArr.push(doc.data());
});
}).then(() => {
setData(dataArr);
});
}, []);
return (
<>
<Header />
<Grid container spacing={2} style={{ alignItems: "center", width: "80%", margin: "auto", marginTop: 50 }}>
{data && data.map((item, idx) => {
return (
<Grid key={idx} item xs={12}>
<Card sx={{ width: "80%", margin:"auto" }}>
<CardHeader
avatar={
<Avatar src={item.user_image} />
}
title={item.user_name}
subheader={item.time_stamp.toDate().toDateString()}
/>
<CardMedia
component="img"
height="300"
image={item.image}
alt={item.name}
style={{ width: "100%" }}
/>
<CardContent>
{item.content}
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
</IconButton>
</CardActions>
</Card>
</Grid>
)
})}
</Grid>
</>
)
}
export default Home;
useEffect로 Firebase Firestore의 feeds컬렉션의 모든 문서를 가져와서 유저에게 뿌린다.
얼추 마무리 되었다. 부가 기능만 빨리 추가하고 끝내자.