1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

React + Redux(Redux Toolkit & Firebase)の構成でアプリを作成しました【6】【Quora Clone】

Last updated at Posted at 2022-03-01

環境の準備

①ターミナルでreactアプリケーションを作成する。

$ npx create-react-app <プロジェクト名>
% cd <プロジェクト名>
% npm start

② 必要なパッケージをインストールする。

$ npm install @material-ui/core  @material-ui/icons 
$ npm install firebase
$ npm install react-modal
$ npm install react-flip-move
$ npm install @reduxjs/toolkit --save

③ 不要なファイルを削除する。

App.css, App.test.js, logo.svg, setupTest.js

コンポーネント・ファイル構成

 src
  ├── components
       ├── auth
            ├── Login.css
            └── Login.js
       ├── Feed.js
       ├── Navbar.js
       ├── Post.js
       ├── Quora.js
       └── QuoraBox.js
  ├── css
       ├── Feed.css
       ├── Navbar.css
       ├── Post.css
       ├── Quora.css
       └── QuoraBox.css
  ├── features
       ├── questionSlice.js
       ├── store.js
       └── userSlice.js
  ├── App.css
  ├── App.js
  ├── firebase.js
  ├── index.css
  ├── index.js

Firebaseを設定する

公式サイト:Firebase

Reactを基本からまとめてみた【22】【Firebaseを使ったログイン機能の実装 ③】

src/components/auth/Login.css と Login.js を編集

src/components/auth/Login.css

@import url("https://fonts.googleapis.com/css2?family=Cedarville+Cursive&display=swap");

.login {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100vh;
  background: url("http://qsfs.fs.quoracdn.net/-4-images.home.illo_1920.png-26-e9e6fbe02d908942.png");
}

.login_container {
  display: flex;
  flex-direction: column;
  padding: 20px;
  width: 700px;
  background-color: rgb(255, 255, 255);
  border-radius: 10px;
}

.login_logo {
  display: flex;
  justify-content: center;
}

.login_desc {
  display: flex;
  flex-direction: column;
  justify-content: center;
  margin-top: 10px;
}

.login_desc > h3 {
  margin-top: 10px;
  text-align: center;
  font-size: xx-large;
  font-family: "Cedarville Cursive", cursive;
}
.login_desc > p {
  color: gray;
  text-align: center;
  font-weight: 500;
  font-size: 20px;
}

.login_auth {
  display: flex;
  margin-top: 50px;
}

.login_authOptions {
  display: flex;
  flex: 0.5;
  flex-direction: column;
  padding: 20px;
  border-right: 1px solid lightgray;
}

.login_authOption {
  display: flex;
  align-items: center;
  padding: 7px;
  margin-bottom: 15px;
  border: 1px solid lightgray;
  border-radius: 5px;
  cursor: pointer;
}

.login_authOption:hover {
  background-color: rgb(226, 226, 226);
}

.login_authOption > img {
  height: 30px;
  object-fit: contain;
}

.login_authOption > p {
  margin-left: 11px;
}

.login_authDesc {
  display: flex;
  align-items: center;
  padding: 7px;
  margin-bottom: 15px;
}
.login_authDesc > p {
  font-size: small;
  color: gray;
}

.login_emailPass {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 20px;
  flex: 0.5;
  margin-left: 20px;
}

.login_label > h4 {
  padding: 5px;
}

.login_inputFields {
  display: flex;
  flex-direction: column;
  padding: 5px;
}

.login_inputField {
  padding: 5px;
}

.login_inputField > input {
  padding: 10px;
  width: 100%;
  outline: none;
  border: none;
  background-color: rgb(236, 236, 236);
}

.login_forgButt {
  padding-left: 10px;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.login_forgButt > small {
  color: gray;
  cursor: pointer;
}

.login_forgButt > small:hover {
  color: blue;
  text-decoration: underline;
}

.login_forgButt > button {
  padding: 9px;
  border-radius: 5px;
  border: none;
  outline: none;
  background-color: blue;
  color: white;
  font-weight: bold;
  font-size: 15px;
  cursor: pointer;
}

.login_emailPass > button {
  padding: 9px;
  border-radius: 5px;
  border: none;
  outline: none;
  background-color: blue;
  color: white;
  font-weight: bold;
  font-size: 15px;
  cursor: pointer;
  margin-top: 10px;
}

.login_lang {
  display: flex;
  align-items: center;
  justify-content: center;
  border-top: 1px solid lightgray;
  border-bottom: 1px solid lightgray;
  margin-top: 30px;
  padding: 10px;
}

.login_lang > p {
  color: gray;
  margin-right: 5px;
  cursor: pointer;
  font-size: 13px;
}

.login_lang > p:hover {
  color: gray;
  margin-right: 5px;
  text-decoration: underline;
}

.login_lang > .MuiSvgIcon-root {
  color: gray;
}

.login_footer {
  padding-top: 10px;
  display: flex;
  align-items: center;
  justify-content: space-evenly;
}

.login_footer > p {
  color: gray;
  font-size: small;
  cursor: pointer;
}

.login_footer > p:hover {
  text-decoration: underline;
}

src/components/auth/Login.js
import React, { useState } from "react";
import "./Login.css";
import { auth, provider } from "../../firebase";

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const signIn = () => {
    auth.signInWithPopup(provider).catch((e) => {
      alert(e.message);
    });
  };

  const handleSignIn = (e) => {
    e.preventDefault();

    auth
      .signInWithEmailAndPassword(email, password)
      .then((auth) => {
        console.log(auth);
      })
      .catch((e) => alert(e.message));
  };

  const registerSignIn = (e) => {
    e.preventDefault();
    auth
      .createUserWithEmailAndPassword(email, password)
      .then((auth) => {
        if (auth) {
          console.log(auth);
        }
      })
      .catch((e) => alert(e.message));
  };
  return (
    <div className="login">
      <div className="login_container">
        <div className="login_logo">
          {/* <img
            src="155278-ffc107.png"
            src="dolphin-2-64.png"
            src="logo2.png"
            alt=""
          /> */}
        </div>
        <div className="login_desc">
          <p>Journey to the Unknown, 100 Years of Life</p>
          <p style={{ color: "green", fontSize: "25px" }}></p>
          <h1>Welcome to Dogaben</h1>
        </div>
        <div className="login_auth">
          <div className="login_authOptions">
            <div className="login_authOption">
              <img
                className="login__googleAuth"
                src="https://media-public.canva.com/MADnBiAubGA/3/screen.svg"
                alt=""
              />
              <p onClick={signIn}>Continue With Google</p>
            </div>
            <div className="login_authOption">
              <img
                className="login_googleAuth"
                src="https://1000logos.net/wp-content/uploads/2016/11/Facebook-logo-500x350.png"
                alt=""
              />
              <span>Continue With Facebook</span>
            </div>
            <div className="login_authDesc">
              <p>
                <span style={{ color: "blue", cursor: "pointer" }}>
                  Sign Up With Email
                </span>
                
                <span style={{ color: "blue", cursor: "pointer" }}>
                  Terms of Service{" "}
                </span>
                and{" "}
                <span style={{ color: "blue", cursor: "pointer" }}>
                  Privacy Policy
                </span>
                .
              </p>
            </div>
          </div>
          <div className="login_emailPass">
            <div className="login_label">
              <h4>Login</h4>
            </div>
            <div className="login_inputFields">
              <div className="login_inputField">
                <input
                  value={email}
                  onChange={(e) => setEmail(e.target.value)}
                  type="text"
                  placeholder="Email"
                />
              </div>
              <div className="login_inputField">
                <input
                  value={password}
                  onChange={(e) => setPassword(e.target.value)}
                  type="password"
                  placeholder="Password"
                />
              </div>
            </div>
            <div className="login_forgButt">
              <small>Forgot Password?</small>
              <button onClick={handleSignIn}>Login</button>
            </div>
            <button onClick={registerSignIn}>Register</button>
          </div>
        </div>
      </div>
    </div>
  );
};
export default Login;

src / components / Feed.js と Navbar.js と Post.js と Quora.js と QuoraBox.js を編集

src/components/Feed.js
import React, { useEffect, useState } from "react";
import "../css/Feed.css";
import QuoraBox from "./QuoraBox";
import Post from "./Post";
import db from "../firebase";

const Feed = () => {
  const [posts, setPosts] = useState([]);
  useEffect(() => {
    db.collection("questions")
      .orderBy("timestamp", "desc")
      .onSnapshot((snapshot) =>
        setPosts(
          snapshot.docs.map((doc) => ({
            id: doc.id,
            question: doc.data(),
          }))
        )
      );
  }, []);
  return (
    <div className="feed">
      <QuoraBox />
      {posts.map(({ id, question }) => (
        <Post
          key={id}
          Id={id}
          image={question.imageUrl}
          question={question.question}
          timestamp={question.timestamp}
          quoraUser={question.user}
        />
      ))}
    </div>
  );
};
export default Feed;
src/components/Navbar.js

import React, { useState } from "react";
import "../css/Navbar.css";
import HomeIcon from "@material-ui/icons/Home";
import FeaturedPlayListOutlinedIcon from "@material-ui/icons/FeaturedPlayListOutlined";
import {
  AssignmentTurnedInOutlined,
  ExpandMore,
  Link,
  NotificationsOutlined,
  PeopleAltOutlined,
  Search,
} from "@material-ui/icons";
import { Avatar, Button, Input } from "@material-ui/core";
import { useSelector } from "react-redux";
import { selectUser } from "../features/userSlice";
//import { auth, db } from "../firebase";
import { auth } from "../firebase";
import db from "../firebase";
import Modal from "react-modal";
import firebase from "../firebase";

const Navbar = () => {
  const user = useSelector(selectUser);
  const [opneModal, setOpenModal] = useState(false);
  const [input, setInput] = useState("");
  const [inputUrl, setInputUrl] = useState("");

  const handleQuestion = (e) => {
    e.preventDefault();

    setOpenModal(false);

    db.collection("questions").add({
      question: input,
      imageUrl: inputUrl,
      timestamp: firebase.firestore.FieldValue.serverTimestamp(),
      user: user,
    });
    setInput("");
    setInputUrl("");
  };

  return (
    <div className="qHeader">
      <Avatar />
      {/* {display} */}
      <h1>'s Dogaben</h1>
      <div className="qHeader-content">
        <div className="qHeader_logo">
          <img
            src="https://video-public.canva.com/VAD8lt3jPyI/v/ec7205f25c.gif"
            alt="logo"
          />
        </div>
        <div className="qHeader_icons">
          <div className="qHeader_icon">
            <HomeIcon />
          </div>
          <div className="qHeader_icon">
            <FeaturedPlayListOutlinedIcon />
          </div>
          <div className="qHeader_icon">
            <AssignmentTurnedInOutlined />
          </div>
          <div className="qHeader_icon">
            <PeopleAltOutlined />
          </div>
          <div className="qHeader_icon">
            <NotificationsOutlined />
          </div>
        </div>
        <div className="qHeader_input">
          <Search />
          <input type="text" placeholder="Search questions" />
        </div>
        <div className="qHeader_Rem">
          <div className="qHeader_avatar">
            <Avatar onClick={() => auth.signOut()} src={user.photo} />
          </div>
        </div>

        <Button onClick={() => setOpenModal(true)} variant="outlined">
          Add Question
        </Button>
        <Modal
          isOpen={opneModal}
          onRequestClose={() => setOpenModal(false)}
          shouldCloseOnOverlayClick={false}
          style={{
            overlay: {
              width: 700,
              height: 600,
              backgroundColor: "rgba(0,0,0,0.8)",
              zIndex: "1000",
              top: "50%",
              left: "50%",
              marginTop: "-300px",
              marginLeft: "-350px",
            },
          }}
        >
          <div className="modal_title">
            <h5>Add Question</h5>
            <h5>Share Link</h5>
            <div className="modal_info">
              <Avatar className="avatar" src={user.photo} />
              <p>{user.displayName ? user.displayName : user.email}asked</p>
              <div className="modal_scope">
                <PeopleAltOutlined />
                <p>Public</p>
                <ExpandMore />
              </div>
            </div>
            <div className="modal_Field">
              <Input
                required
                value={input}
                onChange={(e) => setInput(e.target.value)}
                type="text"
                PeopleAltOutlined="start your question with 'What,'How','Why' etc.'"
              />
            </div>
            <div className="modal_fieldLink">
              <Link />
              <input
                value={inputUrl}
                onChange={(e) => setInputUrl(e.target.value)}
                type="text"
                PeopleAltOutlined="Optional:inclue a link that gives context"
              ></input>
            </div>
            <div className="modal_buttons">
              <button onClick={() => setOpenModal(false)}>Close</button>
              <button onClick={handleQuestion} type="submit" className="add">
                Add Question
              </button>
            </div>
          </div>
        </Modal>
      </div>
    </div>
  );
};

export default Navbar;
src/components/Post.js
import { Avatar } from "@material-ui/core";
import React, { useEffect, useState } from "react";
import "../css/Post.css";
import "../css/Navbar.css";
import Modal from "react-modal";
import { useDispatch, useSelector } from "react-redux";
import {
  selectQuestionId,
  selectQuestionName,
  setQuestionInfo,
} from "../features/questionSlice";
import db from "../firebase";
import { selectUser } from "../features/userSlice";
import firebase from "../firebase";

Modal.setAppElement("#root");

const Post = ({ Id, question, image, timestamp, quoraUser }) => {
  const user = useSelector(selectUser);
  const [openModal, setOpenModal] = useState(false);
  const dispatch = useDispatch();

  const questionId = useSelector(selectQuestionId);
  const questionName = useSelector(selectQuestionName);
  const [answer, setAnswer] = useState("");
  const [getAnswer, setGetAnswer] = useState([]);

  useEffect(() => {
    if (questionId) {
      db.collection("questions")
        .doc(questionId)
        .collection("answer")
        .orderBy("timestamp", "desc")
        .onSnapshot((snapshot) =>
          setGetAnswer(
            snapshot.docs.map((doc) => ({
              id: doc.id,
              answer: doc.data(),
            }))
          )
        );
    }
  });

  const handleAnswer = (e) => {
    e.preventDefault();

    if (questionId) {
      db.collection("questions").doc(questionId).collection("answer").add({
        questionId: questionId,
        timestamp: firebase.firestore.FieldValue.serverTimestamp(),
        answer: answer,
        user: user,
      });
      console.log(questionId, questionName);
      setAnswer("");
      setOpenModal(false);
    }
  };

  return (
    <div
      className="post"
      onClick={() =>
        dispatch(
          setQuestionInfo({
            question: Id,
            questionName: question,
          })
        )
      }
    >
      <div className="post_info">
        <Avatar src={quoraUser.photo} />
        <h5>
          {quoraUser.displayName ? quoraUser.displayName : quoraUser.email}
        </h5>
        <small>{new Date(timestamp?.toDate()).toLocaleDateString()}</small>
      </div>
      <div className="post_body">
        <div className="post_question">
          <p>{question}</p>
          <button onClick={() => setOpenModal(true)} className="post_btnAnswer">
            Answer
          </button>
          <Modal
            isOpen={openModal}
            onRequestClose={() => setOpenModal(false)}
            shouldCloseOnOverlayClick={false}
            style={{
              overlay: {
                width: 700,
                height: 600,
                backgroundColor: "rgba(0,0,0,0.8)",
                zIndex: "1000",
                top: "50%",
                left: "50%",
                marginTop: "-300px",
                marginLeft: "-350px",
              },
            }}
          >
            <div className="post_question">
              <h1>{question}</h1>
              <p>
                asked by{" "}
                <span className="name">
                  {quoraUser.displayName
                    ? quoraUser.displayName
                    : quoraUser.email}
                </span>
                on
                <span className="name">
                  {new Date(timestamp?.toDate()).toLocaleString()}
                </span>
              </p>
            </div>
            <div className="modal_answer">
              <textarea
                required
                value={answer}
                onChange={(e) => setAnswer(e.target.value)}
                placeholder="Enter Your Answer"
                type="text"
              />
            </div>
            <div className="modal_button">
              <button className="cancle" onClick={() => setOpenModal(false)}>
                Cancel
              </button>
              <button onClick={handleAnswer} type="submit" className="add">
                Add answer
              </button>
            </div>
          </Modal>
          <div className="post_answer">
            {setGetAnswer.map(({ id, answers }) => (
              <p
                key={id}
                style={{ position: "relative", paddingBottom: "5px" }}
              >
                {Id === answer.questionId ? (
                  <span>
                    {answers.answer}
                    <br />
                    <span
                      style={{
                        position: "absolute",
                        color: "gray",
                        fontSize: "small",
                        display: "flex",
                        right: "0px",
                      }}
                    >
                      <span style={{ color: "#b96b27" }}>
                        {answers.user.displayName
                          ? answers.user.displayName
                          : answers.user.email}
                        on{" "}
                        {new Date(answers.timestamp?.toDate()).toLocaleString()}
                      </span>
                    </span>
                  </span>
                ) : (
                  ""
                )}
              </p>
            ))}
          </div>
          <img src={image} alt="" />
        </div>
      </div>
    </div>
  );
};

export default Post;
src/components/Quora.js
import React from "react";
import "../css/Navbar.css";
import Feed from "./Feed";
import Navbar from "./Navbar";

const Quora = () => {
  return (
    <div className="quora">
      <Navbar />
      <div className="quora_content">
        <Feed />
      </div>
    </div>
  );
};

export default Quora;
src/components/QuoraBox.js
import { Avatar } from "@material-ui/core";
import React from "react";
import { useSelector } from "react-redux";
import "../css/QuoraBox.css";
import { selectUser } from "../features/userSlice";

const QuoraBox = () => {
  const user = useSelector(selectUser);
  return (
    <div className="quoraBox">
      <div className="quoraBox_info">
        <Avatar src={user.photo} />
        <h5>{user.displayName}</h5>
      </div>
      <div className="quoraBox_quora">
        <p>What is your question or link?</p>
      </div>
    </div>
  );
};

export default QuoraBox;

src / ccs / Feed.css と Navbar.css と Post.css と Quora.css と QuoraBox.css を編集

src / ccs / Feed.css
.feed {
  display: flex;
  flex-direction: column;
  flex: 0.6;
}
src / ccs /  Navbar.css
.qHeader {
  display: flex;
  align-items: center;
  background-color: #ffd700;
  position: sticky;
  z-index: 1000;
  top: 0px;
  /* box-shadow: 0px 5px 8px -9px rgba(0, 0, 0, 0.5); */
  box-shadow: 2px 3px 6px white;
  justify-content: center;
  padding: 1px;
}

.qHeader-content {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* padding: 5px; */
}

.qHeader_logo > img {
  height: 70px;
  object-fit: contain;
}

.qHeader_icons {
  display: flex;
}

.qHeader_icon {
  padding: 30px;
  font-size: 1.5em;
  cursor: pointer;
  padding: 20px 20px;
}

.qHeader_icon:hover {
  background-color: white;
  border-radius: 200px;
}

.qHeader_icon > .MuiSvgIcon-root {
  color: black;
  font-size: xx-large;
  margin-left: 65px;
  margin-right: 65px;
}

.qHeader_icon:hover > .MuiSvgIcon-root {
  color: #000;
}

/* .qHeader__icons {
        display: flex;
        align-items: center;
        cursor: pointer;
      }
      .qHeader__icons > .active > .MuiSvgIcon-root {
        color: #8f1f1b;
      }
      .qHeader__icon > .MuiSvgIcon-root {
        font-size: xx-large;
        padding: 5px 20px;
        color: gray;
      }
      .qHeader__icon > .MuiSvgIcon-root:hover {
        background-color: rgba(233, 233, 233, 0.5);
        border-radius: 10px;
        color: #b92b27;
      } */

.qHeader_input {
  display: flex;
  align-items: center;
  border: 1px solid black;
  padding: 5px;
  border-radius: 5px;
  margin-left: 5px;
}

.qHeader_input > input {
  background-color: transparent;
  outline: none;
  border: none;
  color: black;
}

.qHeader_input > .MuiSvgIcon-root {
  color: black;
}

/* .qHeader__avatar {
      cursor: pointer;
    } */
.qHeader_Rem {
  display: flex;
  align-items: center;
  margin-left: 25px;
}

.qHeader_Rem > .MuiSvgIcon-root {
  font-size: xx-large;
  color: black;
  margin-left: 25px;
  cursor: pointer;
}
.qHeader_Rem > .MuiSvgIcon-root:hover {
  color: #000;
}

.qHeader_Rem > .MuiButton-root {
  color: white;
  background: #222;
  text-transform: inherit;
  border-radius: 5px;
  margin-left: 25px;
}

.qHeader_Rem > .MuiButton-root:hover {
  color: #222;
  background: rgb(214, 214, 214);
}

.modal_title {
  display: flex;
  align-items: center;
  margin-bottom: 5px;
  border-bottom: 1px solid rgba(187, 187, 187, 0.5);
}

.modal_title > h5 {
  color: gray;
  font-size: 20px;
  cursor: pointer;
  font-weight: 500;
  margin-right: 30px;
}

.modal_title > h5:hover {
  color: #b92b27;
}

.modal_info {
  display: flex;
  align-items: center;
  margin-top: 30px;
}

.modal_info > p {
  margin-left: 10px;
  font-size: small;
  color: gray;
}

.modal_info > .modal__scope {
  display: flex;
  align-items: center;
  color: rgb(98, 98, 98);
  padding: 5px;
  margin-left: 10px;
  background-color: rgb(230, 230, 230);
  border-radius: 33px;
  cursor: pointer;
}

.modal_Field {
  display: flex;
  flex-direction: column;
  margin-top: 30px;
  flex: 1;
}

.modal_Field > .modal__fieldLink {
  color: gray;
  display: flex;
  margin-top: 10px;
  align-items: center;
}

.modal_Field > .modal__fieldLink > input {
  flex: 1;
  border: none;
  outline: none;
  margin-left: 5px;
}

.modal_buttons {
  display: flex;
  flex-direction: column-reverse;
  margin-top: 10px;
  align-items: center;
}

.modal_buttons > .cancle {
  margin-top: 10px;
  border: none;
  outline: none;
  color: black;
  font-weight: 500;
  padding: 10px;
  border-radius: 33px;
  cursor: pointer;
}

.modal_buttons > .cancle:hover {
  color: red;
}

.modal_buttons > .add {
  border: none;
  outline: none;
  margin-top: 10px;
  background-color: #222;
  color: white;
  font-weight: 700;
  padding: 10px;
  border-radius: 33px;
  cursor: pointer;
  width: 50%;
}

.modal_buttons > .add:hover {
  background-color: #eee;
  color: #222;
}

@media only screen and (max-width: 600px) {
  .qHeader_icons {
    display: none;
  }

  .qHeader_input {
    display: none;
  }
}
src / ccs /  Post.css

/* .post {
  display: flex;
  flex-direction: column;
  padding: 30px;
  border: 1px solid black;
  background-color: white;
  border-radius: 5px;
  margin-top: 0px;
}

.post_info {
  display: flex;
  align-items: center;
}

.post_info > h5 {
  margin-left: 10px;
  cursor: pointer;
  font-size: 13px;
}

.post_info > h5:hover {
  text-decoration: underline;
}

.post_info > small {
  margin-left: 10px;
}

.post_body {
  display: flex;
  flex-direction: column;
}

.post_body > .post_question {
  margin-top: 10px;
  font-weight: bold;
  margin-bottom: 0px;
  cursor: pointer;
  display: flex;
}

.post_question > .post_btnAnswer {
  margin-left: auto;
  outline: none;
  border: none;
  border-radius: 5px;
  background-color: black;
  color: white;
  padding: 5px;
}

.post_body > .post_question > p:hover {
  text-decoration: underline;
} */

.post {
  display: flex;
  flex-direction: column;
  padding: 10px;
  background-color: white;
  margin-top: 0px;
  border: 2px solid #ffd700;
  border-radius: 5px;
  max-width: 700px;
  box-shadow: 0px 5px 8px -9px solid rgab(0, 0, 0, 0.5);
}

.post_info {
  display: flex;
  align-items: center;
}

.post_info > h5 {
  margin-left: 10px;
  cursor: pointer;
  font-size: 13px;
}
.post_info > h5:hover {
  text-decoration: underline;
}

.post_info > small {
  margin-left: 10px;
}

.post_body {
  display: flex;
  flex-direction: column;
}

.post_body > .post_question {
  margin-top: 5px;
  font-weight: bold;
  margin-bottom: 5px;
  cursor: pointer;
  display: flex;
  align-items: center;
  flex: 1;
}

.post_question > .post_btnAnswer {
  margin-left: auto;
  cursor: pointer;
  padding: 5px;
  background-color: #222;
  outline: none;
  border: none;
  color: white;
  font-weight: 300;
  font-size: 14px;
  border-radius: 5px;
}

.post_btnAnswer:hover {
  color: #222;
  background: lightgray;
}

.post_question > p:hover {
  text-decoration: underline;
}

.post_body > .post_answer > p {
  margin-bottom: 10px;
}

/* .post__image {
    background: rgb(0, 140, 255);
  } */

/* .post__body > img {
  width: 100%;
  max-height: 400px;
  object-fit: contain;
  background-color: transparent;
  border-radius: 5px;
  cursor: pointer;
  margin-top: 10px;
}

.post__footer {
  display: flex;
  align-items: center;
  margin-top: 5px;
}

.post__footer > .MuiSvgIcon-root {
  color: gray;
  margin-right: 40px;
  cursor: pointer;
}

.post__footerAction {
  background-color: lightgray;
  padding: 5px;
  align-items: center;
  display: flex;
  justify-content: space-around;
  border-radius: 33px;
}
.post__footerAction > .MuiSvgIcon-root {
  color: gray;
  margin-right: 40px;
  cursor: pointer;
}

.post__footerAction > .MuiSvgIcon-root:hover {
  color: rgb(0, 140, 255);
  margin-right: 40px;
}

.post__footerLeft {
  margin-left: auto;
}

.post__footerLeft > .MuiSvgIcon-root {
  color: gray;
  cursor: pointer;
  margin-left: 30px;
}

.modal__question {
  display: flex;
  align-items: center;
  flex-direction: column;
  margin-top: 20px;
}

.modal__question > h1 {
  color: #8f1f1b;
  font-weight: 600;
  margin-bottom: 10px;
}

.modal__question > p {
  color: gray;
  font-size: small;
}

.modal__question > p > .name {
  color: black;
  font-weight: bold;
}

.modal__answer {
  display: flex;
  padding-top: 20px;
  flex: 1;
}

.modal__answer > textarea {
  width: 100%;
  height: 200px;
  padding: 5px;
  font-size: 15px;
  color: black;
}

.modal__button {
  display: flex;
  align-items: center;
  flex-direction: row;
  justify-content: space-between;
  margin-top: 50px;
  width: 100%;
}

.modal__button > .cancle {
  border: none;
  margin-top: 10px;
  outline: none;
  color: gray;
  font-weight: 500;
  padding: 10px;
  border-radius: 33px;
  cursor: pointer;
}

.modal__button > .cancle:hover {
  color: red;
}
.modal__button > .add {
  border: none;
  outline: none;
  margin-top: 5px;
  background-color: #222;
  color: white;
  font-weight: 700;
  padding: 10px;
  border-radius: 33px;
  cursor: pointer;
  width: 50%;
}
.modal__button > .add:hover {
  background-color: #eee;
  color: #222;
}

.react-responsive-modal-modal {
  /* width: 600px; */
/* height: 400px;
}

.quill {
  width: 100%;
  height: 120px;
} */

src / ccs /  Quora.css
.quara_content {
  display: flex;
  justify-content: center;
  margin-top: 50px;
}
src / ccs /  QuoraBox.css
.quoraBox {
  display: flex;
  flex-direction: column;
  padding: 10px;
  border: 2px solid #ffd700;
  background-color: white;
  border-radius: 5px;
  cursor: pointer;
  margin-top: 0px;
  font-weight: bold;
  margin-bottom: 0px;
}
.quoraBox:hover {
  border: 1px solid green;
}
.quoraBox_info {
  display: flex;
  align-items: center;
}
.quoraBox_info > h5 {
  color: rgb(129, 129, 129);
  font-weight: 300;
  margin-left: 10px;
}

.quoraBox_quora {
  display: flex;
  margin-top: 8px;
}

.quoraBox_quora > p {
  color: rgb(129, 129, 129);
  font-weight: 300;
  margin-left: 10px;
}

##src/features/questionSlice.js と store.js と userSlice.js を編集

src/features/questionSlice.js
import { createSlice } from "@reduxjs/toolkit";

export const quesitionSlice = createSlice({
  name: "quesition",
  initialState: {
    questionId: null,
    questionName: null,
  },
  reducers: {
    setQuestionInfo: (state, action) => {
      state.questionId = action.payload.questionId;
      state.questionName = action.payload.questionName;
    },
  },
});

export const { setQuestionInfo } = quesitionSlice.actions;
export const selectQuestionId = (state) => state.question.questionId;
export const selectQuestionName = (state) => state.question.questionName;
export default quesitionSlice.reducers;
src/features/store.js
import { configureStore } from "@reduxja/toolkit";
//import useReducer from "../features/userSlice";
//import questionReducer from "../features/questionSlice";
import useReducer from "./userSlice";
import questionReducer from "./questionSlice";

export default configureStore({
  reducer: {
    user: useReducer,
    question: questionReducer,
  },
});
src/features/userSlice.js
import { createSlice } from "@reduxjs/toolkit";

export const userSlice = createSlice({
  name: "user",
  initialState: {
    user: null,
  },
  reducers: {
    login: (state, action) => {
      state.user = action.payload;
    },
    logout: (state) => {
      state.user = null;
    },
  },
});

export const { login, logout } = userSlice.actions;
export const selectUser = (state) => state.user.user;
export default userSlice.reducer;

##src/ App.css と App.js と firebase.js と index.css と index.jsを編集

src/ App.css
.App {
  margin: 0;
  background-color: rgb(241, 241, 241);
  height: auto;
  width: auto;
}
src/ App.js
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import "./App.css";
import Login from "./components/auth/Login";
import Quora from "./components/Quora";
import { login, logout, selectUser } from "./features/userSlice";
import { auth } from "./firebase";

function App() {
  const user = useSelector(selectUser);
  const dispatch = useDispatch();

  useEffect(() => {
    auth.onAuthStateChanged((authUser) => {
      if (authUser) {
        dispatch(
          login({
            uid: authUser.uid,
            email: authUser.email,
            displayName: authUser.displayName,
            photo: authUser.photoURL,
          })
        );
      } else {
        dispatch(logout());
      }
      console.log(authUser);
    });
  }, [dispatch]);
  return <div className="App">{user ? <Quora /> : <Login />}</div>;
}

export default App;
src/firebase.js
import firebase from "./firebase";

const firebaseConfig = {
  apiKey: "AIzaSyCNRncIO9UDrJ7W84QfWwBDu07p6CxEquY",
  authDomain: "quora-redux-firebase.firebaseapp.com",
  projectId: "quora-redux-firebase",
  storageBucket: "quora-redux-firebase.appspot.com",
  messagingSenderId: "895387063956",
  appId: "1:895387063956:web:a742e07d4c2ebc78750681",
};

const firebaseApp = firebase.initializeApp(firebaseConfig);
const auth = firebase.auth();
const provider = new firebase.auth.GoogleAuthProvider();
const db = firebaseApp.firestore();
export { auth, provider };
export default db;
src/index.css
* {
  margin: 0;
}

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
    "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
    sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

code {
  font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
    monospace;
}
src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);

参考サイト

Part 1 | Full Stack Quora Clone : How to build Quora Clone using React | (Redux & Firebase)
Part 2 | Full Stack Quora Clone : How to build using ReactJS | (Redux & Firebase )
Part 3| Full Stack Quora Clone |React Modal PopUp & Backend Firebase (Pushing Q/A on Cloud Database)

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?