環境の準備
①ターミナルで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 を編集
@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;
}
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 を編集
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;
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;
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;
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;
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 を編集
.feed {
display: flex;
flex-direction: column;
flex: 0.6;
}
.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;
}
}
/* .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;
} */
.quara_content {
display: flex;
justify-content: center;
margin-top: 50px;
}
.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 を編集
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;
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,
},
});
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を編集
.App {
margin: 0;
background-color: rgb(241, 241, 241);
height: auto;
width: auto;
}
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;
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;
* {
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;
}
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)