<firebase 실시간 데이터베이스를 이용한 Card - firebase.js>
import { initializeApp } from "firebase/app";
import { getDatabase } from "firebase/database";
const firebaseConfig = {
apiKey: process.env.REACT_APP_FS_APIKEY,
authDomain: process.env.REACT_APP_FS_AUTHDOMAIN,
databaseURL: process.env.REACT_APP_FS_DATABASEURL,
projectId: process.env.REACT_APP_FS_PROJECTID,
storageBucket: process.env.REACT_APP_FS_STORAGEBUCKET,
messagingSenderId: process.env.REACT_APP_FS_MESSAGINGSENDERID,
appId: process.env.REACT_APP_FS_APPID,
};
const firebaseApp = initializeApp(firebaseConfig);
export default firebaseApp;
export const database = getDatabase(firebaseApp);
<firebase 실시간 데이터베이스를 이용한 Card - Login.jsx>
import React, { useState } from 'react'
import styled from 'styled-components'
import Header from '../include/Header'
import Footer from '../include/Footer'
import { useNavigate } from 'react-router-dom'
import { useDispatch, useSelector } from 'react-redux'
import { setToastMsg } from '../../redux/toastStatus/action'
import { loginGoogle } from '../../service/authLogic'
const LoginDiv = styled.div`
width: 30em;
background-color: white;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
`
const ListUl = styled.ul`
width: 100%;
display: flex;
flex-direction: column;
padding: 0.5rem;
list-style: none;
`
const ItemLi = styled.li`
margin-bottom: 0.5em;
`
const BtnLogin = styled.button`
width: 100%;
height: 2.5em;
font-size: 1.2rem;
border-radius: 1.2rem;
background-color: transparent;
color: #E74646;
cursor: pointer;
border: 0.2rem solid #FA9884;
outline: 0;
&:hover{
background-color: #FA9884;
color: #FFF3E2;
}
`
const Login = () => {
// 구글 로그인 성공시 CardManager화면으로 이동처리에 사용
const navigate = useNavigate()
// 구글 로그인 진행 중 에러 발생하면 에러 메시지 출력 처리 위해 사용
const dispatch = useDispatch()
// 구글 로그인시 auth와 googleProvider 파라미터로 전송위해
const userAuth = useSelector(store => store.userAuth)
const [userId, setUserId] = useState()
const onLogin = async() => {
try {
const result = await loginGoogle(userAuth.auth, userAuth.googleProvider)
console.log(result)
console.log(result.uid)
setUserId(result.uid)
window.localStorage.setItem("userId", result.uid)
navigate({
pathname: '/manager',
state: {id: userId},
})
// window.location.reload()
} catch (error) {
dispatch(setToastMsg(error+': 로그인 오류'))
}
}
return (
<LoginDiv>
<Header />
<section>
<h1>Login</h1>
<ListUl>
<ItemLi><BtnLogin onClick={onLogin}>Google</BtnLogin></ItemLi>
<ItemLi><BtnLogin>Github</BtnLogin></ItemLi>
</ListUl>
</section>
<Footer />
</LoginDiv>
)
}
export default Login
<firebase 실시간 데이터베이스를 이용한 Card - CardManager.jsx>
import React, { useEffect, useState } from 'react'
import Header from '../include/Header'
import Footer from '../include/Footer'
import styled from 'styled-components'
import Preview from './Preview'
import CardEditor from './CardEditor'
import { useSelector } from 'react-redux'
import { database } from '../../service/firebase'
import { useNavigate } from 'react-router-dom'
import { off, onValue, ref, remove, set } from 'firebase/database'
const MakerDiv = styled.div`
width: 100%;
height: 100%;
max-width: 80rem;
display: flex;
flex-direction: column;
background-color: white;
`
const ContainerDiv = styled.div`
display: flex;
flex: 1;
min-height: 0;
`
const CardManager = ({FileInput}) => {
// Realtime database 추가분
console.log(database)
// userId 활용
const userId = window.localStorage.getItem('userId')
console.log(userId)
// 페이지가 렌더링될 때 한 번 요청하기
//조희 처리 전에 userId를 쥐고 있어야함 - 현재는 localStorage에 있음
// 그렇기 때문에 의존성 배열에 userId를 넣으면 안된다
// -> 왜냐하면 userId가 변경될때만 실행되니까
useEffect (() => {
// 사용자별로 네임카드를 관리하기위해 트리에 root로 userId를 사용함
// 다시 card아래 생성된 시간을 10진수로 받아서 라벨 및 id값으로 사용함
const starCountRef = ref(database, `${userId}/card`);
onValue(starCountRef, (snapshot) => {
// 검색된 정보가 담김
const data = snapshot.val();
console.log(data)
// 검색된 정보를 실시간으로 화면과 동기화 처리를 위해서 state훅에 초기화함
setCards(data)
return () => off(starCountRef)
});
}, [])
// auth객체 정보 수집위해 -> userAuth.auth
const {userAuth} = useSelector(store => store)
const [cards, setCards] = useState({});
// 입력과 수정시에 모두 사용함
const createOrUpdateCard = card => {
console.log(userId)
setCards(cards => {
const updated = {...cards}
// 어차피 여기서 id가 오브젝트에 없다면 새로운 것이 추가됨
// 그래서 addCard는 필요없음
updated[card.id] = card
return updated
})
console.log(card.id)
set(ref(database, `${userId}/card/${card.id}`), card);
}
// 데이터셋은 CardManager에 있음 -> 원본은 건들지 않고 복사본 사용 -> 삭제, 추가, 수정
// 삭제 버튼은 CardEditorForm에 있음 - 삭제 대상도 거기 있음
// 자바스크립트는 파라미터 사용가능
// 파라미터 값은 언제 결정? -> 사용자가 삭제버튼을 클릭했을때 - deleteCard함수 호출
// 그때 파라미터로 card를 전달받을 수 있음
// 해당 변수는 함수이다 - 왜냐하면 소문자이니까, 만약 대문자이면 컴포넌트이다
// 삭제대상이 되는 정보를 가진 card 파라미터는 CardEditorForm에서 올라온다 - 버블링과 같은컨셉
const deleteCard = card => { // 삭제하고자하는 카드정보를 여기서 결정할 수 없음 -> CardEditorForm으로 넘겨줌
console.log(card)
setCards(cards => { // 리렌더링 즉시 -> return -> 내 안에 컴포넌트 -> rendering
// 스프링 부트에서 넘어오는 데이터셋은 useState매핑 -> 화면이 다시 그려진다
const updated = {...cards} // 복사 spread 연산자 -> 깊은복사
delete updated[card.id]
return updated // 복사본이 리턴된다
})
remove(ref(database, `${userId}/card/${card.id}`))
}
return (
<MakerDiv>
<Header />
<ContainerDiv>
<CardEditor FileInput={FileInput} cards={cards}
addCard = {createOrUpdateCard}
updateCard = {createOrUpdateCard}
deleteCard={deleteCard} />
<Preview cards={cards} />
</ContainerDiv>
<Footer />
</MakerDiv>
)
}
export default CardManager
<firebase 실시간 데이터베이스를 이용한 Card - CardEditor.jsx>
import React from 'react'
import CardAddForm from './CardAddForm'
import styled from 'styled-components'
import CardEditorForm from './CardEditorForm'
const EditorDiv = styled.div`
flex-basis: 50%;
border-right: 1px solid #9E7676;/* editor와 preview사이에 구분선 넣기 */
padding: 0.5em 2em;
overflow-y: auto;
`
const TitleH1 = styled.h1`
width: 100%;
text-align: center;
margin-bottom: 1em;
color: #594545;
`
const CardEditor = ({ FileInput, cards, deleteCard, addCard, updateCard}) => {
console.log(cards); // 3건 출력 - CardManager.jsx에 선언된 cards, setCards
return (
<EditorDiv>
<TitleH1>Card Editor</TitleH1>
{Object.keys(cards).map(key => (
/* cards 3개 로우에 대해서 한 개 card정보만 전달해야함 */
<CardEditorForm key={key} FileInput={FileInput} card={cards[key]}
updateCard={updateCard} deleteCard={deleteCard} />
))
}
<CardAddForm FileInput={FileInput} addCard={addCard} />
</EditorDiv>
)
}
export default CardEditor
<firebase 실시간 데이터베이스를 이용한 Card - CardAddForm.jsx>
import React, { useRef, useState } from 'react'
import styled from 'styled-components'
import Button from '../common/Button'
const Form = styled.form`
display: flex;
width: 100%;
flex-wrap: wrap; /* 한 줄에 하나씩 떨어질 수 있도록 랩을 주고 */
border-top: 1px solid black;
border-left: 1px solid black;
margin-bottom: 1em;
`
const NameInput = styled.input`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;//#F5EBE0, #FEFCF3
flex: 1 1 30%; /* 30%주어서 한 줄에 3개씩 나오게 하고 */
`
const CompanyInput = styled.input`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
flex: 1 1 30%; /* 30%주어서 한 줄에 3개씩 나오게 하고 */
`
const TitleInput = styled.input`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
flex: 1 1 30%; /* 30%주어서 한 줄에 3개씩 나오게 하고 */
`
const EmailInput = styled.input`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
flex: 1 1 30%; /* 30%주어서 한 줄에 3개씩 나오게 하고 */
`
const ThemeSelect = styled.select`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
flex: 1 1 30%; /* 30%주어서 한 줄에 3개씩 나오게 하고 */
`
const MessageTextArea = styled.textarea`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
`
const FileInputDiv = styled.div`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
`
const CardAddForm = ({FileInput, card, addCard}) => {
//값들을 읽어와서 Card에 추가하기
// html에서 제공하는 form태그 속성으로 백엔드와 연동이 불안정함 - 사용하지않음
const formRef = useRef(); // 폼돔 정보가짐 - 리렌더링이 되더라도 값이 바뀌지 않음
const nameRef = useRef(); // 이름
const companyRef = useRef(); // 회사명
const themeRef = useRef(); // 테마변경
const titleRef = useRef(); // 제목
const emailRef = useRef(); // 이메일
const messageRef = useRef(); // 키워드 작성
const [file, setFile] = useState({ fileName: null, fileURL: null}); // cloudinary
const onFileChange = (file) => {
console.log(file);
setFile({
fileName: file.name,
fileURL: file.url,
});
}
const onSubmit = (event) => {
//이벤트 전이 막기 - button태그는 디폴트가 submit속성을 가짐. - 그래서 화면이 새로고침일어남 -이것을 막아줌
event.preventDefault();
//사용자가 입력한 값을 받아서 카드를 만듦 -> 이제 카드를 추가해주면 된다
const card = {
id: Date.now(),//uuid
name: nameRef.current.value || '', /* 입력된 값이 있으면 쓰고 없으면 빈문자열 치환 */
company: companyRef.current.value || '',
theme: themeRef.current.value,
title: titleRef.current.value || '',
email: emailRef.current.value || '',
message: messageRef.current.value || '',
fileName: file.fileName || '', /* 나중에 제대로 해보자 null이라면 빈문자열*/
fileURL: file.fileURL || '', /* 나중에 제대로 해보자 */
};
formRef.current.reset();// 즉 사용자가 입력해서 제출하고 나면 폼이다 리셋되도록 이렇게 해줌
setFile({ fileName: null, fileURL: null });
addCard(card)
};
return (
<Form ref={formRef}>
<NameInput ref={nameRef} type="text" name="name" placeholder="Name"/>
<CompanyInput ref={companyRef} type="text" name="company" placeholder="Company"/>
<ThemeSelect ref={themeRef} name="theme" placeholder="Theme">
<option placeholder="light">light</option>
<option placeholder="dark">dark</option>
<option placeholder="colorful">colorful</option>
</ThemeSelect>
<TitleInput ref={titleRef} type="text" name="title" placeholder="Title"/>
<EmailInput ref={emailRef} type="text" name="email" placeholder="Email"/>
<MessageTextArea ref={messageRef} name="message" placeholder="Message"/>
<FileInputDiv>
<FileInput name={file.fileName} onFileChange={onFileChange} />
</FileInputDiv>
<Button name="Add" onClick={onSubmit}/>
</Form>
)
}
export default CardAddForm
<firebase 실시간 데이터베이스를 이용한 Card - CardEditorForm.jsx>
import React from 'react'
import styled from 'styled-components';
import Button from '../common/Button';
import ImageFileInput from '../common/ImageFileInput';
const Form = styled.form`
display: flex;
width: 100%;
flex-wrap: wrap; /* 한 줄에 하나씩 떨어질 수 있도록 랩을 주고 */
border-top: 1px solid black;
border-left: 1px solid black;
margin-bottom: 1em;
`
const NameInput = styled.input`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;//#F5EBE0, #FEFCF3
flex: 1 1 30%; /* 30%주어서 한 줄에 3개씩 나오게 하고 */
`
const CompanyInput = styled.input`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
flex: 1 1 30%; /* 30%주어서 한 줄에 3개씩 나오게 하고 */
`
const TitleInput = styled.input`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
flex: 1 1 30%; /* 30%주어서 한 줄에 3개씩 나오게 하고 */
`
const EmailInput = styled.input`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
flex: 1 1 30%; /* 30%주어서 한 줄에 3개씩 나오게 하고 */
`
const ThemeSelect = styled.select`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
flex: 1 1 30%; /* 30%주어서 한 줄에 3개씩 나오게 하고 */
`
const MessageTextArea = styled.textarea`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
`
const FileInputDiv = styled.div`
font-size: 0.8rem;
width: 100%;
border: 0;
padding: 0.5em;
border-bottom: 1px solid black;
border-right: 1px solid black;
background: #F5EBE0;
`
const CardEditorForm = ({ FileInput, card, deleteCard, updateCard}) => {
const {name, company, title, email, message, theme, fileName, fileURL} = card;
// 이미지 파일을 선택하는 것만으로 이벤트 감지가 발생하여 CardManager에 정의된
// createOrUpdateCard가 호출됨
// CardEditorForm에서는 updateCard함수가 호출되고 파라미터로 변경된 card정보를
// CardManager까지 props로 넘겨받은 것을 역으로 호출되는 구조임
// 이렇게 역으로 보내는 방법이 필요한 이유는 변경된 정보를 쥐고있는 것은 CardManager가 아니라
// CardEditorForm이기때문 - 소스분석필요
const onFileChange = (file) => {
console.log(file);
// useState에 초기화된 배열에 사용자가 선택한 fileName과 fileURL을 추가해서 배열을 수정해준다
updateCard({
...card,
fileName: file.name,
fileURL: file.url,
})
} // end of onFileChange
const onChange = (event) => {
if(event.currentTarget == null) {
return;
}
//브라우저에서 기본적인 이벤트 처리를 하지 않도록 처리한다
event.preventDefault();
updateCard( {
...card,
[event.currentTarget.name]: event.currentTarget.value,
});
};
//maker.jsx에서 deleteCard호출할때 실제 기능 처리할 코드임- 이걸 해야 삭제됨
const onSubmit = (e) => {
// 이벤트 버블링 차단 -> 하지 않으면 화면 새로고침이 일어남(무한루프 주의)
e.preventDefault()
// 여기서 호출되는 함수는 CardManager에서 옴
// 파라미터엔 삭제할 card
// 삭제버튼은 여기 있지만
deleteCard(card)
};
return (
<Form>
<NameInput
type="text" name="name"
value={name}
onChange={onChange}
/>
<CompanyInput
type="text"
name="company"
value={company}
onChange={onChange}
/>
<ThemeSelect
name="theme"
value={theme}
onChange={onChange}
>
<option value="light">light</option>
<option value="dark">dark</option>
<option value="colorful">colorful</option>
</ThemeSelect>
<TitleInput
type="text" name="title"
value={title}
onChange={onChange}
/>
<EmailInput
type="text"
name="email"
value={email}
onChange={onChange}
/>
<MessageTextArea
name="message"
value={message}
onChange={onChange}
>
</MessageTextArea>
<FileInputDiv>
<FileInput name={fileName} onFileChange={onFileChange} />
</FileInputDiv>
<Button name="Delete" onClick={onSubmit}/>
</Form>
)
}
export default CardEditorForm
'국비학원 > 수업기록' 카테고리의 다른 글
국비 지원 개발자 과정_Day98 (0) | 2023.04.19 |
---|---|
국비 지원 개발자 과정_Day97 (0) | 2023.04.18 |
국비 지원 개발자 과정_Day95 (0) | 2023.04.14 |
국비 지원 개발자 과정_Day94 (0) | 2023.04.13 |
국비 지원 개발자 과정_Day93 (0) | 2023.04.12 |
댓글