[ 항해 6주차 회고록] 미니 프로젝트
⚡️ 6주차 시작!
이제 리엑트 숙련까지 마치고 본격적인 백엔드분들과의 협업이 시작되었다.
그런데 첫날부터 문제가 생겼다..ㅠㅠ
이번 기수에 프론트엔드 수가 워낙 적어서 우리팀에 더 들어와주실 분들이 없으셨다..
그래서 담임매니저님께서 어쩔수없이 우리팀에서는 프론트엔드는 나 혼자서 진행해야 될거 같다고 하셨다.
스코프를 줄여서 진행하라고 하셨지만...
이미 그때 우리팀은 계획과 컨셉을 다 짠 상태이고 애초에 우리가 할 수 있는만큼만 짰기 때문에 더 이상 덜어낼수있는 기능이 없었다.
아니면 잡은 구성자체를 엎을수 밖에.....
그래도 백엔드 분들께서 이번껀 엎고 내가 저번주에 했던거에서 출발해도 된다고 부담가지지 말라고 일을 줄여줄려고 해주셨지만(갬동...) 나만 이렇게 해서는 다른 프론트분들 보다 1주일 뒤쳐지는거 같아서 이 상황에 지기싫어서 속으로 눈물 쥴쥴쥴 흘리면서 진행시킬 수 밖에 없었다(또륵..)
💾 미니 프로젝트 - MODIRA(모디라!)
이번주 우리팀 프로젝트 컨셉은 ‘모디라’는 대구 사투리로 ‘모여라’라는 뜻으로 대구에서 사는 사람들끼리
모여 다양한 활동을 하고 싶은 사람들의 모임을 만들 수 있도록 도와주는 웹서비스였다.
백엔드 분의 아이디어와 내가 제시한 작명이 합쳐져서 초반에 쿵짝쿵짝 진행이 잘됐었다b
Modira
- 작성 : 글쓰기 - [로그인 유저 만 가능]
- 회원 가입
- 일반 로그인
- userId (중복 확인 필요 : BE)
- password (비밀번호 확인 및 비밀번호 체크 : FE)
- nickname (중복 확인 필요 : BE)
- 일반 로그인
- Home 화면 : 다른 사람과 공유됨
* 모디라 와이어 프레임
* 모디라 API 명세서
1. App 컴포넌트 (최상위 컴포넌트)
import './App.css';
import React from 'react';
import { Route, Routes, BrowserRouter } from 'react-router-dom';
import axios from 'axios';
import { Provider } from "react-redux";
import store from './redux/store';
import Main from './components/Main';
import Header from './components/Header';
import LogIn from './components/LogIn';
import SignUp from './components/SignUp';
import Write from './components/Write';
import Detail from './components/Detail';
import GuBtns from './components/GuBtns';
import Local from './components/Local';
function App() {
// 유저 id state ( test 위해서 임시 기본값 줌)
const [users, setUsers] = React.useState({
username: '',
password: '',
});
// 로그인 기본값은 로그인 안되있는 상태로 고정..
const [is_login, setIsLogin] = React.useState(false);
// 로그인 상태 확인 후 로그인이 true인지 false인지 useEffect로 결정해줘야됨.(사이드 이펙트 관리)
// const loginCheck = (user) => { // 유저를 가지고 와서
// if (user) { // 만약에 유저가 있다면?
// setIsLogin(true); // useState의 setIsLogin값을 true로 바꿔준다.
// setUsers(user.username)
// }
// else { // 만약에 유저가 없다면?
// setIsLogin(false); // useState의 setIsLogin값을 false로 바꿔준다.
// setUsers(null)
// }
// };
// 로그인 상태 확인 후 로그인이 true인지 false인지 useEffect로 결정해줘야됨.(사이드 이펙트 관리)
const loginCheck = (users) => { // 유저를 가지고 와서
if (axios.get("http://sparta-9kyo.shop/user/login").then(response => {
console.log(response)
document.cookie = `loginCookie=${response.data}`
})) { // 만약에 user의 login 있다면?
setIsLogin(true); // useState의 setIsLogin값을 true로 바꿔준다.
setUsers(users.username)
}
else { // 만약에 유저가 없다면?
setIsLogin(false); // useState의 setIsLogin값을 false로 바꿔준다.
setUsers(null)
}
};
// React.useEffect(() => { // useEffect는 main함수에서만 작동함
// loginCheck;
// }, []);
///////////////////// write 정보들 ////////////////////////////
// card 정보 배열 state
const [boardArray, setBoardArray] = React.useState({
writeTitle: '',
writeIntroduce: '',
});
////////// SignIn 정보들 ///////////
// axios 강의 내용
// const callSomething = async () => {
// let data = {
// "day": "일",
// "sleep_time": "10시간"
// }
// const response = await fetch("http://localhost:5001/sleep_times",{ // 이게 get 요청, fetch는 promise를 반환
// method: "POST",
// headers: {
// "Content-Type": "application/json; charset = utf-8" //(어플리케이션) json으로 보내줄거야
// },
// body: JSON.stringify(data) //바디값에 넣어줄 값으로 data를 넣어줌.
// }) // fetch에 들어간 options는 객체 형식으로 만들어주면 됨
// console.log(response)
// }
// const callSomethingAxios = () => {
// axios({
// medhod: "get",
// url: "http://localhost:5001/sleep_times" // url을 어디서 가지고 올꺼야?
// }.then(response =>{
// console.log(response)
// })) // config 설정
// }
// React.useEffect(() => {
// callSomethingAxios();
// }, []);
// axios.get("http://localhost:5001/sleep_times").then(response => {
// console.log(response)
// }); // get에 요청할 주소만 넣어주면됨
return (
<div className="App">
<Provider store={store}>
<BrowserRouter>
<Header is_login={is_login}/>
<GuBtns />
<Routes>
<Route exact path="/" element={<Main />} />
{/* 여기가 카드 들어가는 페이지 useParams 걸기 */}
{/* 여기에 카드 import해서 임시로 만들어놓기 */}
{/* img 프레임에 비율 맞추기 */}
<Route path="/local/:localcode" element={<Local />} />
<Route path="/login" element={<LogIn is_login={is_login} setIsLogin={setIsLogin} users={users} setUsers={setUsers} />} />
<Route path="/signin" element={<SignUp />} />
<Route path="/write" element={<Write boardArray={boardArray} setBoardArray={setBoardArray}/>} />
<Route path="/detail/:id" element={<Detail />} />
</Routes>
</BrowserRouter>
</Provider>
</div>
);
}
export default App;
2. Header 컴포넌트
import '../css/Header.css';
import {useNavigate} from 'react-router-dom';
import {useSelector} from 'react-redux';
function Header() {
const navigate = useNavigate();
const loginId = useSelector((state)=>(state.userReducer.username));
// console.log(userid);
return (
<div className="Header">
<div className='wrap'>
<div className='logo'>
<h3 onClick={()=>navigate("/")}>MODIRA</h3>
</div>
<div className='login_btns'>
{/* onClick시 바로 함수 적용시 렌더링 되버림 */}
<button>{loginId? loginId:"항해99"}님</button>
<button >로그아웃</button>
{/* <button onClick={()=>navigate("/login")}>로그인</button>
<button onClick={()=>navigate("/signin")}>회원가입</button> */}
</div>
</div>
</div>
);
}
export default Header;
3. Gu Button 컴포넌트 ( 페이지 상단에 나눠진 대구시 '구' 카테고리)
import '../css/GuBtns.css';
import {useNavigate} from 'react-router-dom';
function GuBtns() {
const navigate = useNavigate();
return (
<div className="GuBtns">
<div className='wrap1'>
<button onClick={()=>{navigate("/local/1")}}>수성구</button>
<button onClick={()=>{navigate("/local/2")}}>동 구</button>
<button onClick={()=>{navigate("/local/3")}}>북 구</button>
<button onClick={()=>{navigate("/local/4")}}>중 구</button>
<button onClick={()=>{navigate("/local/5")}}>서 구</button>
<button onClick={()=>{navigate("/local/6")}}>달서구</button>
<button onClick={()=>{navigate("/local/7")}}>남구</button>
</div>
</div>
);
}
export default GuBtns;
4. First Main 컴포넌트 (웹서비스 처음 들어가면 있는 )
import '../css/FirstMain.css';
function FirstMain() {
return (
<div className="FirstMain">
<div className='font'>
<h3>대구 사람들</h3>
<h3>여기로 다</h3>
<h3>MODIRA!</h3>
</div>
<div className='wrap2'>
</div>
</div>
);
}
export default FirstMain;
5. Main 컴포넌트
import '../css/Main.css';
// import {useNavigate} from 'react-router-dom';
import FirstMain from './FirstMain';
function Main() {
return (
<div className="Main">
<div className='wrap'>
<FirstMain/>
</div>
</div>
);
}
export default Main;
6. Local 컴포넌트 (메인에 들어갈 모임 카드 내용)
import '../css/Local.css';
import { useNavigate, useParams } from 'react-router-dom';
import AddBtn from './AddBtn';
import axios from 'axios';
import { useEffect, useState } from 'react';
function Local() {
const [cardListApi,setCardListApi] = useState({
list:[
]
});
const navigate = useNavigate();
const params = useParams();
// const my_lists = useSelector((state)=>state.write.list);
let cardList = {
list: [
{ id :1, title: "모각코 하실 프론트앤드 구해요", count: "6" },
{ id :2, title: "보드 타러 가실분!", count: "3" },
{ id :3, title: "저녁에 같이 저녁 드실분들 구해요", count: "4" },
{ id :4, title: "치맥 페스티벌 가실분 구해요", count: "2" },
{ id :5, title: "보드 타러 가실분", count: "4" },
]
}
useEffect(() => {
// axios 요청하기(axios의 response)
axios.get(`http://sparta-9kyo.shop/api/post/local/${params.localcode}`).then(response => {
cardList.list = [...response.data]
setCardListApi(cardList)
console.log('api 호출 성공',params.localcode)
});
},[params.localcode]) // [] : 안에 있는 값이 바뀌면 다시 useEffect 작동
console.log(cardListApi);
return (
<div className="Local">
<AddBtn />
<div className='card_list'>
{/* 초기 값 배열에서 list까지 가야지 에러 안뜬다. */}
{cardListApi.list.map((list, index) => {
return (
<div className="Card" key={index} onClick={() => { navigate(`/detail/${list.id}`)}}>
<div className='img_wrap'>
<div></div>
</div>
<div className='text_wrap'>
<h3>{list.title}</h3>
<button>{list.partyNum}명</button>
</div>
</div>
);
})}
</div>
</div>
);
}
export default Local;
7. Detail 컴포넌트 (모임 카드 선택시 보이는 상세내용)
import '../css/Detail.css';
import {useNavigate, useParams} from 'react-router-dom';
import { useEffect, useState } from 'react';
import axios from 'axios';
function Detail() {
// 파라미터를 가져오기 위해 react-router-dom의 useParams 훅을 사용!
const params = useParams();
const navigate = useNavigate(); //App에서 동적라우팅 한거 (:localcode) 받아온거
console.log(params)
// 위에 주소창에 http://localhost:3000/local/1 라고쓰면
// 콘솔에 {local: ':1'}가 뜸
//useNavigate
// <Local /> -> 전체 게시물
// [게시물, 게시물, 게시물]
// 게시물 하나를 클릭 할때 ->
// 해당 게시물의 id를 참조해서 라우팅
// navigate(‘/detail/id’)
// 2.
// /detail/:id -> <Detail />
// useParams() -> id 불러오기
// /api/post/{id}/detail 로 상세데이터 불러오기
// const [detailData, setDetailData] = useState(null)
// setDetailData(받아온값)
// detailData.title
// detailData.desc
// const [idParams,setIdParams] = useState(params.id);
const [detailData, setDetailData] = useState({});
useEffect(() => {
axios.get(`http://sparta-9kyo.shop/api/post/${params.id}`).then(response => {
console.log(response)
setDetailData({...response.data})
console.log('api 호출 성공', params.id)
});
}, [params.id]) // [] : 안에 있는 값이 바뀌면 다시 useEffect 작동
console.log(detailData)
// contents: "수성구를 위한 데이터"
// createdAt: "2022-07-28 14:19:29"
// dones: false
// id: 25
// joinNum: 0
// locationName: 1
// modifiedAt: "2022-07-28 14:19:29"
// partyNum: 10
// title: "수성구"
// userName: "user3"
return (
<div className="Detail">
<div className='detail_wrap'>
<div className='detail_img'>
<div></div>
</div>
<div className='detail_title'>
<h1>{detailData.title}</h1>
</div>
<div className='detail_count'>
<button>{detailData.partyNum} 명</button>
</div>
<div className='detail_write'>
<p>
{detailData.contents}
</p>
</div>
<div className='back_btn'>
<button onClick={()=>{navigate(-1)}}>다른 MODIRA 보러갈래요!</button>
</div>
</div>
</div>
);
}
export default Detail;
8. Add Button 컴포넌트 ( 메인에서 작성글 페이지로 이동하는 버튼 )
import React from 'react';
import '../css/AddBtn.css';
// font-awesome 사용방법
// 장점 : 폰트 속성 적용하듯 css로 손쉽게 색, 크기를 변경할 수 있으며 절대로 깨지지 않는다.
// https://jae04099.tistory.com/entry/React-%EB%A6%AC%EC%95%A1%ED%8A%B8%EC%97%90-font-awesome-%EC%95%84%EC%9D%B4%EC%BD%98-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faPenToSquare } from "@fortawesome/free-solid-svg-icons";
import {useNavigate} from "react-router-dom";
function AddBtn(props) {
const navigate = useNavigate();
return (
<div className="Add_Btn" onClick={()=>{navigate("/write")}}>
<div className='add_icon'>
<FontAwesomeIcon icon={faPenToSquare} className="checkpen" />
</div>
</div>
);
}
export default AddBtn;
9. Write 컴포넌트 ( 모임 주최하는 작성글 페이지 )
import React, { useState, useRef } from 'react';
import '../css/Write.css';
import {useDispatch, useSelector} from 'react-redux';
import axios from 'axios';
function Write({boardArray, setBoardArray}) {
let dispatch = useDispatch();
// gu select option에 SelectGu 배열을 순서에 맞게 각각 setSelect에 저장했다.
const [selectGu, setSelectGu] = React.useState('');
const [selectCount, setSelectCount] = React.useState('');
const [previewImg, setPreviewImg] = React.useState();
const [UploadImg, setUploadImg] = React.useState();
console.log(selectGu);
console.log(selectCount);
// 못생긴 file type input을 button에 연동해주기 위한 ref
const imageInput = React.useRef(null);
const writeTitle_ref = React.useRef(null);
const write_introduce_ref = React.useRef(null);
const { writeTitle, writeIntroduce } = boardArray; // 비구조화 할당을 통해 값 추출
const onChange = e => {
const { value, name } = e.target; // 우선 e.target 에서 name 과 value 를 추출
setBoardArray({
...boardArray, // 기존의 inputValue 객체를 복사한 뒤
[name]: value // name 키를 가진 값을 value 로 설정
});
console.log(boardArray)
}
// img 업로드
const img_change = (e) => {
console.log(e.target.files);
const reader = new FileReader();
reader.readAsDataURL(e.target.files[0]); // 내가 올릴 img
console.log(URL.createObjectURL(e.target.files[0]),'여기요!')
reader.onload = () => {
setPreviewImg(reader.result);
};
setUploadImg(e.target.files[0])
}
// 못생긴 file type 내가 만든 버튼 연동(버튼클릭시 file type input태그에 클릭이벤트를 걸어준다.)
const click_file=()=>{
imageInput.current.click();
}
const WriteClick = (e) =>{
// input 비어있으면 경고창 띄우기
// if( !writeTitle_ref.current.value || !imageInput.current.value || !write_introduce_ref.current.value ){
// alert("빈칸을 채워주세요!");
// return;
// }
// console.log(select);
//URL.createObjectURL(e.target.files[0]
console.log(selectGu,selectCount,UploadImg,boardArray);
axios.post("http://sparta-9kyo.shop/api/post", {
"title" : writeTitle_ref.current.value,
// "imageUrl" : UploadImg,
"contents" : write_introduce_ref.current.value,
"partyNum" : selectCount,
"locationName" : selectGu
}).then(response => { // 성공
console.log(response) // 찍히면 성공상태
}).catch(error => { // 실패
alert('작성해주세요!')// 실패동작
}); // get에 요청할 주소만 넣어주면됨
// const writeText = () => {
// axios({
// medhod: "get",
// url: "http://localhost:5001/sleep_times" // url을 어디서 가지고 올꺼야?
// }.then(response =>{
// console.log(response)
// })) // config 설정
// }
}
return (
<div className="Write">
<div className='write_wrap'>
<div className='main_title'>
<h1>오늘의 MODIRA</h1>
</div>
<div className='write_title'>
<h1>모임 제목</h1>
<input type="text" placeholder='제목은 14자까지 작성됩니다' ref={writeTitle_ref}
name="writeTitle" onChange={onChange} value={writeTitle}></input>
</div>
<div className='choice_gu'>
<h1>모임 장소</h1>
<select className='select_gu' onChange={e => { setSelectGu(e.target.value)}}>
<option value='수성구'>수성구</option>
<option value='동구'>동 구</option>
<option value='서구'>서 구</option>
<option value='중구'>중 구</option>
<option value='남구'>남 구</option>
<option value='북구'>북 구</option>
<option value='달서구'>달서구</option>
</select>
</div>
<div className='choice_count' onChange={e => { setSelectCount(e.target.value)}}>
<h1>모임 최대인원</h1>
<select className='select_count'>
<option value={2}>2명</option>
<option value={3}>3명</option>
<option value={4}>4명</option>
<option value={5}>5명</option>
<option value={6}>6명</option>
<option value={7}>7명</option>
<option value={8}>8명</option>
</select>
</div>
<div className='write_introduce'>
<h1>모임 소개</h1>
<textarea type="text" ref={write_introduce_ref}
name="writeIntroduce" onChange={onChange} value={writeIntroduce}/>
</div>
{/* <div className='fine_img'> */}
{/* <input type="file" placeholder='사진을 선택해주세요' className='file' onChange={img_change} ref={imageInput}/>
<button className='button' onClick={click_file}>사진 선택</button>
<img src={previewImg} className='preview_img'></img>
</div> */}
<div className='write_btn'>
<button onClick={WriteClick}>등록하기</button>
</div>
</div>
</div>
);
}
export default Write;
10. Sign Up 컴포넌트 ( 회원가입 페이지 )
import '../css/SignUp.css';
import React, { useState } from 'react';
import axios from 'axios';
function SignUp() {
const [inputValue, setInputValue] = useState({
username: '',
password: '',
password2: ''
});
const id_ref = React.useRef(null);
const pw_ref = React.useRef(null);
const re_pw_ref = React.useRef(null);
const { username, password, password2 } = inputValue; // 비구조화 할당을 통해 값 추출
const onChange = e => {
const { value, name } = e.target; // 우선 e.target 에서 name 과 value 를 추출
setInputValue({
...inputValue, // 기존의 inputValue 객체를 복사한 뒤
[name]: value // name 키를 가진 값을 value 로 설정
});
}
console.log(inputValue);
// const callSingUpAxios = () => {
// axios({
// medhod: "get",
// url: "http://sparta-9kyo.shop/api/post" // url을 어디서 가지고 올꺼야?
// }.then(response => {
// console.log(response)
// })) // config 설정
// }
const onClick = () => {
console.log(inputValue);
// 아이디 & 비밀번호 유효성 검사
// 이 로직은 상태가 아닌 값 자체를 나타내는 로직이기 때문에 변수로 나타낸다.
// 정규식에서 특수문자가 있으면 true 되야되는데 앞에 !있어서 false로 나옴!
const regExp = /[\{\}\[\]\/?.,;:|\)*~`!^\-_+<>@\#$%&\\\=\(\'\"]/gi;
const isValid = !regExp.test(inputValue.username);
// const isValid = inputValue.username.includes('a-Z') && inputValue.username.includes('0-9');
// 비밀번호 특수문자 검사를 위한 정규식표현.
const specialLetter = inputValue.password.search(/[`~!@@#$%^&*|₩₩₩'₩";:₩/?]/gi);
// 특수문자 1자 이상, 전체 8자 이상일것.
const isValidPassword = inputValue.password.length >= 8 && specialLetter >= 1;
// 비밀번호 check
const Passwordright = inputValue.password.length === inputValue.password2.length;
console.log(isValid);
console.log(isValidPassword);
console.log(Passwordright);
// 형식에 맞지않으면 경고창
if(isValid === true && isValidPassword === true && Passwordright===false){
alert('비밀번호 check 해주세요!')
}
else if(isValid === true && isValidPassword === true && Passwordright===true){
alert('새로운 modira 회원님 환영합니다!')
}
else if(isValid === false ){
alert('아이디 형식이 맞지 않습니다!')
}
else if(isValidPassword === false){
alert('비밀번호 형식이 맞지 않습니다!')
}
axios.post("http://sparta-9kyo.shop/user/signup", {
username: id_ref.current.value,
password: pw_ref.current.value,
password2: re_pw_ref.current.value,
}).then(response => {
console.log(response)
}); // get에 요청할 주소만 넣어주면됨
}
// 체크박스 검사(초기상태는 체크 안되어있으니 false)
// const [checkBoxActive, setCheckboxActive] = useState(false);
// const isCheckBoxClicked = () => {
// setCheckboxActive(!checkBoxActive); // checkBoxActive의 앞에 !(not)로 인해 클릭이 될 때 마다 상태가 false -> true 다시 false -> true 이런식으로 바뀜
// };
return (
<div className="SignUp">
<div className='sign_wrap'>
<div className='sign_title'>
<h1>회원가입</h1>
</div>
<hr className='sign_hr' />
<div className='sign_wrap_bottom'>
<div className='signup_id'>
<h1>ID</h1>
<input type="text" placeholder=' 아이디를 입력해주세요 ' ref={id_ref}
name="userid" onChange={onChange} value={username} />
</div>
<div className='re_signup_id'>
<h1></h1>
<button>ID 중복확인</button>
</div>
{/* <div className='signup_name'>
<h1>닉네임</h1>
<input type="text" placeholder=' 닉네임을 입력해주세요' ref={name_ref}/>
</div> */}
<div className='signup_pw'>
<h1>PW</h1>
<input type="password" placeholder=' 특수문자 1자 이상, 전체 8자 이상' ref={pw_ref}
name="password" onChange={onChange} value={password} />
</div>
<div className='signup_re_pw'>
<h1>PW<br />확인</h1>
<input type="password" placeholder=' 비밀번호를 다시 입력해주세요' ref={re_pw_ref}
name="passwordCheck" onChange={onChange} value={password2} />
</div>
<div className='signup_btn'>
<button onClick={onClick}>회원가입 하기</button>
</div>
</div>
</div>
</div>
);
}
export default SignUp;
11. Log In 컴포넌트 ( 로그인 페이지 )
import '../css/LogIn.css';
import React, { useEffect } from 'react';
import {useDispatch, useSelector} from 'react-redux';
import axios from 'axios';
function LogIn({ is_login, setIsLogin, setUsers, users }) {
let dispatch = useDispatch();
// axios.post("http://sparta-9kyo.shop/user/signup", {
// username: id_ref.current.value,
// password: pw_ref.current.value,
// password2: re_pw_ref.current.value,
// }).then(response => {
// console.log(response)
// }); // get에 요청할 주소만 넣어주면됨
const id_ref = React.useRef(null);
const pw_ref = React.useRef(null);
const { username, password } = users; // 비구조화 할당을 통해 값 추출
const loginId = useSelector((state) => (state.userReducer.username)); // 어떤 데이터를 가지고오고 싶은지?
console.log(loginId);
const onChange = e => {
const { value, name } = e.target; // 우선 e.target 에서 name 과 value 를 추출
setUsers({
...users, // 기존의 inputValue 객체를 복사한 뒤
[name]: value // name 키를 가진 값을 value 로 설정
});
}
// 1. input에 입력되는 로그인과 비밀번호 값을 ref으로 받아서 가져옴
// 2. 그 ref값을 로그인하기 버튼 온클릭에 넣어줌
//로그인하기 버튼 눌렀을때 보낼준비 마치기.(console.log에 아이디,비번값 찍히는지)
const LogInState = () => {
console.log(users)
axios.post("http://sparta-9kyo.shop/user/login", {
username: id_ref.current.value,
password: pw_ref.current.value,
}).then(response => { // 성공
console.log(response) // 찍히면 성공상태
document.cookie = `loginCookie=${response.data}` // 쿠키에 토큰 저장
alert('로그인 성공!')
dispatch({ type: 'user/LOGIN', payload: users }) // 성공후 상단에 아이디가 뜨게 설정
}).catch(error => { // 실패
alert('아이디 비밀번호가 맞지 않습니다')// 실패동작
}); // get에 요청할 주소만 넣어주면됨
}
// img는 firestore에 업로드 해서 url을 백엔드에 보내준다.
return (
<div className="login">
<div className='login_wrap'>
<div className='login_title'>
<h1>로그인</h1>
</div>
<hr className='login_hr' />
<div className='login_id'>
<h1>ID</h1>
<input type="text" placeholder='아이디를 입력해주세요' ref={id_ref}
name="username" onChange={onChange} value={username}/>
</div>
<div className='login_pw'>
<h1>PW</h1>
<input type="password" placeholder='비밀번호를 입력해주세요' ref={pw_ref}
name="password" onChange={onChange} value={password}/>
</div>
<div className='login_btn'>
<button onClick={LogInState}>로그인 하기</button>
</div>
</div>
</div>
);
}
export default LogIn;
12. REDUX - Store
//store는 Reducer을 뭉친 rootReducer로 만든다.
import {createStore, combineReducers} from "redux";
import userReducer from "./moduls/login.js"; // 리듀셔 가져오고
const rootReducer = combineReducers({userReducer}); //리듀서들을 묶어준다.(지금은 하나밖에 없어서 하나만)
// rootReducer랑 createStore 엮어서 store 만들어줌
const store = createStore(rootReducer);
export default store;
13. REDUX (moduls)- login ( 로그인시 상단에 로그인한 유저의 ID를 띄우기 위해 유저 ID를 저장한다. )
// login.js
// Actions (액션 타입 정해주는 곳 / 별 의미는 없고 이름은 내가 정해주면 되는거였음...)
const LOGIN = 'user/LOGIN';
//초기값
const initialState = {
username: '',
}
// Action Creators(액션 생성 함수 만들어주는 곳)
export function userLogin(user) { // 액션 생성 함수는 액션 객체를 리턴해줘야 됨 / ()안에는 추가할 값
return { type: LOGIN, user: user }; // 딕셔너리 형(앞에는 액션타입 뒤에는 무엇을 추가해!라는 내용)
}
// { type: 'user/LOGIN', user: user };
// Reducer
export default function userReducer(state = initialState, action = {}) { // 파라미터 = {} : 기본값을 주는것 (파라미터에 값이 안들어온다면 빈 딕셔너리 일거라는것을 알려주는것, 오류막기 )
switch (action.type) { //switch case : ~~할때 ~~해!
case "user/LOGIN": // case안에서 return해주는 어떤 값이 새로운 state값이 됨!
console.log(action)
const keepId_data = action.payload.username;
return { username: keepId_data };
default:
return state;
}
}
// side effects, only as applicable
// e.g. thunks, epics, etc
// export function getWidget() {
// return dispatch => get('/widget').then(widget => dispatch(updateWidget(widget)))
// }
14. REDUX (moduls)- write ( 작성한 글을 메인에 모임카드에 추가해주려고 했지만... 시간상 여기는 미완성이다ㅠ )
// write->card.js
// Actions (액션 타입 정해주는 곳 / 별 의미는 없고 이름은 내가 정해주면 되는거였음...)
const WRITE = 'write/WRITE';
//초기값
const initialState = {
list: [{
writeTitle: '',
writeIntroduce: '',
}]
}
// Action Creators(액션 생성 함수 만들어주는 곳)
export function userWrite(write) { // 액션 생성 함수는 액션 객체를 리턴해줘야 됨 / ()안에는 추가할 값
return { type: WRITE, write: write }; // 딕셔너리 형(앞에는 액션타입 뒤에는 무엇을 추가해!라는 내용)
}
// { type: 'user/LOGIN', user: user };
// Reducer
export default function writeReducer(state = initialState, action = {}) { // 파라미터 = {} : 기본값을 주는것 (파라미터에 값이 안들어온다면 빈 딕셔너리 일거라는것을 알려주는것, 오류막기 )
switch (action.type) { //switch case : ~~할때 ~~해!
case "write/WRITE": // case안에서 return해주는 어떤 값이 새로운 state값이 됨!
console.log(action)
const keepWrite_data = [action.payload];
console.log(keepWrite_data);
return { list: keepWrite_data };
default:
return state;
}
}
// side effects, only as applicable
// e.g. thunks, epics, etc
// export function getWidget() {
// return dispatch => get('/widget').then(widget => dispatch(updateWidget(widget)))
// }
[ 첫 협업을 하며 느낀 점 (스스로 아쉬웠던 점/ 보완하고 싶은 점)]
1. 스스로 아쉬웠던 점 :
이번에 닥쳐오는 기능구현 때문에 백엔드 분들과 제대로된 현업을 못했었던거 같아서 아쉬웠다.
2. 보완하고 싶은 점
로그인, 회원가입 기능을 쿠키로 해보다가 결국 어려워서 하다가 시간이 모자라서 구현이 덜되었다.
(세션스토리지로 해볼껄.....) 그걸 보완해보고 싶다.
👏 6주차를 마무리하며 👏
정말 매일매일 답답함과 부담감에 엄청 마음고생하고 눈물이 났던 구간이었지만 그래도 혼자서 프론트엔드로
총 5일정도 구현한거 치고는 선빵했다고 생각이 든다.
그 와중에 새벽에 급하게 한시간정도 들여서 first main에 들어갈 그림도 그렸다v
(오랜만에 그림그리는거라 한시간동안 완전 힐링!!!)
그리고 항해 서비스가 아직 몇년 안되서 그런지 사람이 부족할때의 대처가 아직은 불안정한거 같아서 지금까지중 제일 아쉬웠던 점 같다.
다른 기수분들은 나처럼 팀플인데 프론트쪽은 오로지 나혼자만드는 서러운 일을 겪지 않았으면 좋겠다는 생각 뿐이다.