**매 주차 강의자료 시작에 PDF파일을 올려두었어요!**
[수업 목표]
[목차]
💡 모든 토글을 열고 닫는 단축키 Windows : `Ctrl` + `alt` + `t` Mac : `⌘` + `⌥` + `t`// 액션 타입
const LOAD = "bucket/LOAD";
...
// 액션 생성 함수
export function loadBucket(bucket_list){
return {type: LOAD, bucket_list};
}
import {
collection,
doc,
getDoc,
getDocs,
addDoc,
updateDoc,
deleteDoc,
} from "firebase/firestore";
import {db} from "../../firebase";
// 파이어베이스랑 통신하는 부분
export const loadBucketFB = () => {
return async function (dispatch) {
// 데이터를 가져와요!
const bucket_data = await getDocs(collection(db, "bucket"));
let bucket_list = [];
// 하나씩 우리가 쓸 수 있는 배열 데이터로 만들어줍시다!
bucket_data.forEach((b) => {
// 콘솔로 확인해요!
console.log(b.id, b.data());
bucket_list.push({ id: b.id, ...b.data() });
});
// 잘 만들어졌는 지 리스트도 확인해봐요! :)
console.log(bucket_list);
dispatch(loadBucket(bucket_list));
}
}case "bucket/LOAD": {
return {list: action.bucket_list}
}// App.js
...
// 잠깐!! loadBucketFB를 import해오는 거 잊지말기!
React.useEffect( () => {
dispatch(loadBucketFB());
}, []);
...저는 머테리얼 UI의 아이콘을 사용해서 만들어 볼게요!
import React from "react";
import styled from "styled-components";
import {Eco} from "@material-ui/icons";
const Spinner = (props) => {
return (
<Outter>
<Eco style={{ color: "#673ab7", fontSize: "150px" }} />
</Outter>
);
}
const Outter = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background-color: #ede2ff;
`;
export default Spinner;
//bucket.js
...
const initialState = {
is_loaded: false,
list: [],
};
...
case "bucket/LOAD": {
return { list: action.bucket, is_loaded: true };
}...
import { useDispatch, useSelector } from "react-redux";
...
import Spinner from "./Spinner";
...
function App() {
const text = React.useRef(null);
const dispatch = useDispatch();
const is_loaded = useSelector(state => state.bucket.is_loaded);
React.useEffect( () => {
dispatch(loadBucketFB());
}, []);
const addBucketList = () => {
dispatch(addBucketFB({ text: text.current.value, completed: false }));
};
return (
<div className="App">
...
{/* 인풋박스와 추가하기 버튼을 넣어줬어요. */}
<Input>
<input type="text" ref={text} />
<button onClick={addBucketList}>추가하기</button>
</Input>
{!is_loaded && <Spinner />}
</div>
);
}
...
export default App;




yarn add global firebase-tools#웹브라우저가 열리고 내 구글 계정을 물어볼거예요. 로그인해줍니다.
yarn firebase login
#로그인 후 init!
yarn firebase init


{
"hosting": {
"public": "build",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
]
}
}yarn firebase deploy



import { createStore, combineReducers, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import quiz from "./modules/quiz";
import rank from "./modules/rank";
import { createBrowserHistory } from "history";
export const history = createBrowserHistory();
const middlewares = [thunk];
const enhancer = applyMiddleware(...middlewares);
const rootReducer = combineReducers({ quiz, rank });
const store = createStore(rootReducer, enhancer);
export default store;import firebase from "firebase/app";
import "firebase/firestore";
const firebaseConfig = {
apiKey: "AIzaSyDVt53_A0kwA5NZ_KVLW3btY5yOYN40IeE",
authDomain: "friend-test-3b9ff.firebaseapp.com",
databaseURL: "https://friend-test-3b9ff.firebaseio.com",
projectId: "friend-test-3b9ff",
storageBucket: "friend-test-3b9ff.appspot.com",
messagingSenderId: "535393314714",
appId: "1:535393314714:web:4ef23f250ca87d7de17c23",
measurementId: "G-M852M85RP0",
};
firebase.initializeApp(firebaseConfig);
const firestore = firebase.firestore();
export { firestore };import React from "react";
import styled from "styled-components";
import { useSelector, useDispatch } from "react-redux";
import { resetAnswer } from "./redux/modules/quiz";
import {getRankFB} from "./redux/modules/rank";
import Spinner from "./Spinner";
const Ranking = (props) => {
const dispatch = useDispatch();
const _ranking = useSelector((state) => state.rank.ranking);
const is_loaded = useSelector((state) => state.rank.is_loaded);
// Array 내장 함수 sort로 정렬하자!
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
const user_rank = React.useRef(null);
React.useEffect(() => {
dispatch(getRankFB());
if(!user_rank.current){
return;
}
window.scrollTo({top: user_rank.current.offsetTop, left: 0, behavior: "smooth"});
}, []);
const ranking = _ranking.sort((a, b) => {
// 높은 수가 맨 앞으로 오도록!
return b.score - a.score;
});
if(!is_loaded){
return (<Spinner/>);
}
return (
<RankContainer>
<Topbar>
<p>
<span>{ranking.length}명</span>의 사람들 중 당신은?
</p>
</Topbar>
<RankWrap>
{ranking.map((r, idx) => {
if (r.current) {
return (
<RankItem key={idx} highlight={true} ref={user_rank}>
<RankNum>{idx + 1}등</RankNum>
<RankUser>
<p>
<b>{r.name}</b>
</p>
<p>{r.message}</p>
</RankUser>
</RankItem>
);
}
return (
<RankItem key={idx}>
<RankNum>{idx + 1}등</RankNum>
<RankUser>
<p>
<b>{r.name}</b>
</p>
<p>{r.message}</p>
</RankUser>
</RankItem>
);
})}
</RankWrap>
<Button
onClick={() => {
dispatch(resetAnswer());
window.location.href = "/";
}}
>
다시 하기
</Button>
</RankContainer>
);
};
const RankContainer = styled.div`
width: 100%;
padding-bottom: 100px;
`;
const Topbar = styled.div`
position: fixed;
top: 0;
left: 0;
width: 100vw;
min-height: 50px;
border-bottom: 1px solid #ddd;
background-color: #fff;
& > p {
text-align: center;
}
& > p > span {
border-radius: 30px;
background-color: #fef5d4;
font-weight: 600;
padding: 4px 8px;
}
`;
const RankWrap = styled.div`
display: flex;
flex-direction: column;
width: 100%;
margin-top: 58px;
`;
const RankItem = styled.div`
width: 80vw;
margin: 8px auto;
display: flex;
border-radius: 5px;
border: 1px solid #ddd;
padding: 8px 16px;
align-items: center;
background-color: ${(props) => (props.highlight ? "#ffd6aa" : "#ffffff")};
`;
const RankNum = styled.div`
text-align: center;
font-size: 2em;
font-weight: 600;
padding: 0px 16px 0px 0px;
border-right: 1px solid #ddd;
`;
const RankUser = styled.div`
padding: 8px 16px;
text-align: left;
& > p {
&:first-child > b {
border-bottom: 2px solid #212121;
}
margin: 0px 0px 8px 0px;
}
`;
const Button = styled.button`
position: fixed;
bottom: 5vh;
left: 0;
padding: 8px 24px;
background-color: ${(props) => (props.outlined ? "#ffffff" : "#dadafc")};
border-radius: 30px;
margin: 0px 10vw;
border: 1px solid #dadafc;
width: 80vw;
`;
export default Ranking;import React from "react";
import img from "./scc_img01.png";
import { useDispatch, useSelector } from "react-redux";
import { addRank, addRankFB } from "./redux/modules/rank";
const Message = (props) => {
const dispatch = useDispatch();
const name = useSelector((state) => state.quiz.name);
const answers = useSelector((state) => state.quiz.answers);
const user_name = useSelector((state) => state.rank.user_name);
const input_text = React.useRef(null);
// 정답만 걸러내기
let correct = answers.filter((answer) => {
return answer;
});
// 점수 계산하기
let score = (correct.length / answers.length) * 100;
// 컬러셋 참고: https://www.shutterstock.com/ko/blog/pastel-color-palettes-rococo-trend/
return (
<div
style={{
display: "flex",
height: "100vh",
width: "100vw",
overflow: "hidden",
padding: "16px",
boxSizing: "border-box",
}}
>
<div
className="outter"
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
flexDirection: "column",
height: "100vh",
width: "100vw",
overflow: "hidden",
padding: "0px 10vw",
boxSizing: "border-box",
maxWidth: "400px",
margin: "0px auto",
}}
>
<img
src={img}
style={{ width: "80%", margin: "-70px 16px 48px 16px" }}
/>
<h1 style={{ fontSize: "1.5em", margin: "0px", lineHeight: "1.4" }}>
<span
style={{
backgroundColor: "#fef5d4",
padding: "5px 10px",
borderRadius: "30px",
}}
>
{name}
</span>
에게 한마디
</h1>
<input
ref={input_text}
type="text"
style={{
padding: "10px",
margin: "24px 0px",
border: "1px solid #dadafc",
borderRadius: "30px",
width: "100%",
}}
placeholder="한 마디 적기"
/>
<button
onClick={() => {
let rank_info = {
score: parseInt(score),
name: user_name,
message: input_text.current.value,
current: true,
};
// 랭킹 정보 넣기
// dispatch(addRank(rank_info));
dispatch(addRankFB(rank_info));
// 주소 이동
// 시간 차를 두고 이동 시켜줘요.
window.setTimeout(() => {
props.history.push("/ranking");
}, 1000);
}}
style={{
padding: "8px 24px",
backgroundColor: "#dadafc",
borderRadius: "30px",
border: "#dadafc",
}}
>
한마디하고 랭킹 보러 가기
</button>
</div>
</div>
);
};
export default Message;import React from "react";
import styled from "styled-components";
import img from "./scc_img01.png";
const Spinner = (props) => {
return (
<Outter>
<img src={img} />
</Outter>
);
};
const Outter = styled.div`
background-color: #df402c88;
width: 100vw;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
& img {
width: 150px;
}
`;
export default Spinner;Copyright ⓒ TeamSparta All rights reserved.