본문 바로가기
국비학원/수업기록

국비 지원 개발자 과정_Day96

by 루팽 2023. 4. 17.

<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

댓글