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컬렉션의 모든 문서를 가져와서 유저에게 뿌린다.

얼추 마무리 되었다. 부가 기능만 빨리 추가하고 끝내자.