Archive: 2021

0

React로 Toast UI Editor 사용해보기

React로 Toast UI Editor 사용해보기개인 프로젝트도 하고 싶고 나만의 블로그를 만들고 싶은 마음에 UI도 찾아보고 Editor도 찾고 있었었다. 개인적으로 Editor를 찾고 있던 기준은 첫번째는 Markdown 언어가 사용 가능해야 하고 두번째는 이미지 업로드가 가능한 Editor를 찾고 있던 와중에 NHN에서 제공하는 Toast UI Editor를 찾게 됐다. 기능들이 너무너무 맘에 들어 사용해보기로 했다. Toast UI Editor에서는 내가 가장 원했던 Markdown 문법이 사용 가능 했고, 이미지 업로드도 가능 했다.!!! 그리고 코드블럭에 마음에 드는 Syntax Highlighter를 적용할 수 있는 장점과 다양한 기능들이 있어 너무나도 맘에 들었다. 플러그인 설치하기yarn add @toast-ui/react-editor Write.jsx import React from "react";import { Editor } from "@toast-ui/react-editor";import "@toast-ui/editor/dist/toastui-editor.css";const Write = () => { return ( <Editor initialValue="hello react editor world!" previewStyle="vertical" height="600px" initialEditType="markdown" useCommandShortcut={true} /> );};export default Write; 마지막으로 App.js에 새로만든 Write.jsx 컴포넌트를 추가하면 화면에서 Toast UI가 나타나는 것을 확인할 수 있다.

0

JPA 프로그래밍(기본편) 2 - JPA 설정하기

JPA 프로그래밍(기본편) 2 - JPA 설정하기Entity 생성하기Entity 어노테이션을 통해 JPA가 관리하는 객체임을 명시해준다. @Entitypublic class Member { @Id private long id; private String name; public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; }} EntityManagerFactory 생성하기 JPA를 사용하기 위해 persistence.xml에 JPA 설정 정보를 넣어 줬다. 해당 정보를 사용하기 위해서 EntityManagerFactory를 생성해주도록 한다. EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("hello"); EntityManager 생성하기EntityManagerFactory를 이용해 EntityManager를 생성해준다.

0

JPA 프로그래밍(기본편) 1 - JPA 설정하기

JPA 프로그래밍(기본편) 1 - JPA 설정하기의존성 추가하기JPA를 사용하기 위해서 JPA를 구현한 hibernate 라이브러리를 사용한다. DB로는 메모리 DB인 H2 DataBase를 사용하도록 한다. <!-- JPA 하이버네이트 --><dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>5.3.10.Final</version></dependency><!-- H2 데이터베이스 --><dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.4.200</version></dependency> JPA Setting 하기resource/META-INF/persistence.xml JPA 표준 문법 설정 javax.persistence.jdbc.driver : 사용하고자 하는 DB 드라이버를 설정한다. javax.persistence.jdbc.user : DB에 접근하기 위한 Username javax.persistence.jdbc.password : DB에 접근하기 위한 Password javax.persistence.jdbc.url : 접근하고자 하는 DataBase 경로 hibernate 전용 문법 설정

0

리엑트 로그인 페이지 만들기

리엑트 로그인 페이지 만들기import React, { useState, useCallback } from "react";import styled from "styled-components";import axios from "axios";const LoginFragment = styled.div` display:flex; justify-content:center; align-items:center; width:400px; height:400px; position: relative; /* 추후 박스 하단에 추가 버튼을 위치시키기 위한 설정 */ background: white; border-radius: 16px; box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.04); margin: 0 auto;`;const LoginInputs = () => { const [id, setId] = useState(""); const [password, setPassword] = useState(""); const onChangeId = useCallback((e) => { setId(e.target.value); }, []); const onChangePassword = useCallback((e) => { setPassword(e.target.value); }, []); const onSubmit = useCallback((e) => { e.preventDefault(); let data = { username: id, password: password, } console.log(data); axios.post("/api/users/login", data) .then((response => { console.log(response.data) })); }, [id, password]) return ( <LoginFragment> <form style= {{ display: "flex", flexDirection: "column", }} onSubmit={onSubmit} > <label htmlFor="user-id">아이디</label> <input name="user-id" value={id} onChange={onChangeId} /> <label htmlFor="user-password">비밀번호</label> <input name="user-password" type="password" value={password} onChange={onChangePassword} /> <br /> <button type="submit">로그인</button> </form> </LoginFragment > )}export default LoginInputs; import React from "react"import { styled, createGlobalStyle } from "styled-components";import LoginInputs from "./components/LoginInputs";const GlobalStyle = createGlobalStyle` body { background: #e9ecef; }`;function App() { return ( <div> <GlobalStyle /> <LoginInputs /> </div> )}export default App; CORS 해결하기const { createProxyMiddleware } = require("http-proxy-middleware");module.exports = (app) => { app.use( "/api", createProxyMiddleware({ target: "http://localhost:8080", changeOrigin: true, }) );};

0

리엑트 Todo List 만들기 3 - Context API를 이용해 상태 관리하기

리엑트 Todo List 만들기 3 - Context API를 이용해 상태 관리하기 리엑트 Todo List 만들기 2 - 리스트 항목 만들기 리엑트 Todo List 만들기 1 - 기본 템플릿 만들기 리엑트 Todo List 만들기 3 - Context API를 이용해 상태 관리하기Context API 사용 설정하기Reducer 생성function todoReducer(state, action) { switch (action.type) { case "CREATE": return state.concat(action.todo); case "TOGGLE": return state.map((todo) => todo.id === action.id ? { ...todo, done: !todo.done } : todo ); case "REMOVE": return state.filter((todo) => todo.id !== action.id); default: throw new Error(`Unhandled action type: ${action.type}`); }} Conext API 적용하기const TodoStateContext = createContext();const TodoDispatchContext = createContext();const TodoNextIdContext = createContext();export function TodoProvider({ children }) { const [state, dispatch] = useReducer(todoReducer, initialTodos); const nextId = useRef(5); return ( <TodoStateContext.Provider value={state}> <TodoDispatchContext.Provider value={dispatch}> <TodoNextIdContext.Provider value={nextId}> {children} </TodoNextIdContext.Provider> </TodoDispatchContext.Provider> </TodoStateContext.Provider> );} Context 사용을 위한 Custom Hook 생성export function useTodoState() { return useContext(TodoStateContext);}export function useTodoDispatch() { return useContext(TodoDispatchContext);}export function useTodoNextId() { return useContext(TodoNextIdContext);}

0

리엑트 Todo List 만들기 2 - 리스트 항목 만들기

리엑트 Todo List 만들기 3 - Context API를 이용해 상태 관리하기 리엑트 Todo List 만들기 2 - 리스트 항목 만들기 리엑트 Todo List 만들기 1 - 기본 템플릿 만들기 리엑트 Todo List 만들기 2- 리스트 항목 만들기 TodoCreate.js import React, { useState } from 'react';import styled, { css } from 'styled-components';import { MdAdd } from 'react-icons/md';const CircleButton = styled.button` background: #38d9a9; &:hover { background: #63e6be; } &:active { background: #20c997; } z-index: 5; cursor: pointer; width: 80px; height: 80px; display: block; align-items: center; justify-content: center; font-size: 60px; position: absolute; left: 50%; bottom: 0px; transform: translate(-50%, 50%); color: white; border-radius: 50%; border: none; outline: none; display: flex; align-items: center; justify-content: center; transition: 0.125s all ease-in; ${props => props.open && css` background: #ff6b6b; &:hover { background: #ff8787; } &:active { background: #fa5252; } transform: translate(-50%, 50%) rotate(45deg); `}`;const InsertFormPositioner = styled.div` width: 100%; bottom: 0; left: 0; position: absolute;`;const InsertForm = styled.form` background: #f8f9fa; padding-left: 32px; padding-top: 32px; padding-right: 32px; padding-bottom: 72px; border-bottom-left-radius: 16px; border-bottom-right-radius: 16px; border-top: 1px solid #e9ecef;`;const Input = styled.input` padding: 12px; border-radius: 4px; border: 1px solid #dee2e6; width: 100%; outline: none; font-size: 18px; box-sizing: border-box;`;function TodoCreate() { const [open, setOpen] = useState(false); const onToggle = () => setOpen(!open); const isOpen = (open) =>{ if(open){ return( <InsertFormPositioner> <InsertForm> <Input autoFocus placeholder="할 일을 입력 후, Enter 를 누르세요" /> </InsertForm> </InsertFormPositioner>) }else{ return null; } } return ( <div> {isOpen(open)} <CircleButton onClick={onToggle} open={open}> <MdAdd /> </CircleButton> </div> );}export default TodoCreate; TodoItem.js import React from "react";import styled, { css } from "styled-components";import { MdDone, MdDelete } from "react-icons/md";import { useTodoDispatch } from "../TodoContext";const Remove = styled.div` display: flex; align-items: center; justify-content: center; color: #dee2e6; font-size: 24px; cursor: pointer; opacity: 0; &:hover { color: #ff6b6b; }`;const TodoItemBlock = styled.div` display: flex; align-items: center; padding-top: 12px; padding-bottom: 12px; &:hover { ${Remove} { opacity: 1; } }`;const CheckCircle = styled.div` width: 32px; height: 32px; border-radius: 16px; border: 1px solid #ced4da; font-size: 24px; display: flex; align-items: center; justify-content: center; margin-right: 20px; cursor: pointer; ${(props) => props.done && css` border: 1px solid #38d9a9; color: #38d9a9; `}`;const Text = styled.div` flex: 1; font-size: 21px; color: #495057; ${(props) => props.done && css` color: #ced4da; `}`;function TodoItem({ id, done, text }) { const dispatch = useTodoDispatch(); const onToggle = () => dispatch({ type: "TOGGLE", id }); const onRemove = () => dispatch({ type: "REMOVE", id }); return ( <TodoItemBlock> <CheckCircle done={done} onClick={onToggle}> {done && <MdDone />} </CheckCircle> <Text done={done}>{text}</Text> <Remove onClick={onRemove}> <MdDelete /> </Remove> </TodoItemBlock> );}export default TodoItem;

0

리엑트 Todo List 만들기 1 - 기본 템플릿 만들기

리엑트 Todo List 만들기 3 - Context API를 이용해 상태 관리하기 리엑트 Todo List 만들기 2 - 리스트 항목 만들기 리엑트 Todo List 만들기 1 - 기본 템플릿 만들기 리엑트 Todo List 만들기 1 - 기본 템플릿 만들기 TodoTemplate.js import React from "react";import styled from "styled-components";const TodoTemplateBlock = styled.div` width: 512px; height: 768px; position: relative; /* 추후 박스 하단에 추가 버튼을 위치시키기 위한 설정 */ background: white; border-radius: 16px; box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.04); margin: 0 auto; /* 페이지 중앙에 나타나도록 설정 */ margin-top: 96px; margin-bottom: 32px; display: flex; flex-direction: column;`;const TodoTemplate = ({ children }) => { return <TodoTemplateBlock>{children}</TodoTemplateBlock>;}export default TodoTemplate; TodoHead.js import React from 'react';import styled from 'styled-components';import { useTodoState } from '../TodoContext';const TodoHeadBlock = styled.div` padding-top: 48px; padding-left: 32px; padding-right: 32px; padding-bottom: 24px; border-bottom: 1px solid #e9ecef; h1 { margin: 0; font-size: 36px; color: #343a40; } .day { margin-top: 4px; color: #868e96; font-size: 21px; } .tasks-left { color: #20c997; font-size: 18px; margin-top: 40px; font-weight: bold; }`;function TodoHead() { const todos = useTodoState(); const undoneTasks = todos.filter(todo => !todo.done); const today = new Date(); const dateString = today.toLocaleDateString('ko-KR', { year: 'numeric', month: 'long', day: 'numeric' }); const dayName = today.toLocaleDateString('ko-KR', { weekday: 'long' }); return ( <TodoHeadBlock> <h1>{dateString}</h1> <div className="day">{dayName}</div> <div className="tasks-left">할 일 {undoneTasks.length}개 남음</div> </TodoHeadBlock> );}export default TodoHead;

0

리엑트 블로그 만들기 15 - Post Card List 만들기 2

리엑트 블로그 만들기 15 - Post Card List 만들기 2 리엑트 블로그 만들기 14 - Post Card List 만들기 리엑트 블로그 만들기 13 - 라우트 리엑트 블로그 만들기 12 - Loading 만들기 리엑트 블로그 만들기 11 - 회원가입 리덕스 작업 리엑트 블로그 만들기 10 - 회원가입 2 리엑트 블로그 만들기 9 - 회원가입 리엑트 블로그 만들기 8 - 로그인 & 로그아웃 리엑트 블로그 만들기 7 - Login Modal 만들기 2 리엑트 블로그 만들기 6 - Login Modal 만들기 1 리엑트 블로그 만들기 5 - 리덕스 세팅하기 리엑트 블로그 만들기 4 - Navbar 작성하기 리엑트 블로그 만들기 3 - Header 작성하기 리엑트 블로그 만들기 2 - Footer 작성하기 리엑트 블로그 만들기 1 - 초기 setting 하기 리엑트 블로그 만들기 15 - Post Card List 만들기 2import { POSTS_LOADING_FAILURE, POSTS_LOADING_REQUEST, POSTS_LOADING_SUCCESS } from "../types";const initialState = { isAuthenticated: null, posts: [], postDetails: "", postCount: "", loading: false, error: "", creatorId: "", categoryFindResult: "", title: "", searchBy: "", searchResult: "",};export default function postReducer(state = initialState, action) { switch (action.type) { case POSTS_LOADING_REQUEST: return { ...state, posts: [], loading: true, } case POSTS_LOADING_SUCCESS: return { ...state, posts: [...state.posts, ...action.payload], loading: false, }; case POSTS_LOADING_FAILURE: return { ...state, loading: false, }; default: return state; };} // POST WRITEexport const POSTS_WRITE_REQUEST = "POST_WRITE_REQUEST";export const POSTS_WRITE_FAILURE = "POST_WRITE_FAILURE";export const POSTS_WRITE_SUCCESS = "POST_WRITE_SUCCESS";// POST LOADINGexport const POSTS_LOADING_REQUEST = "POST_LOADING_REQUEST";export const POSTS_LOADING_FAILURE = "POST_LOADING_FAILURE";export const POSTS_LOADING_SUCCESS = "POST_LOADING_SUCCESS"; import { combineReducers } from "redux";import { connectRouter } from "connected-react-router";import authReducer from "./authReducer.js";import postReducer from "./postReducer.js";import commentReducer from "./commentReducer.js";const createRootReducer = (history) => combineReducers({ router: connectRouter(history), auth: authReducer, post: postReducer, comment: commentReducer, });export default createRootReducer; import axios from "axios";import { put, call, takeEvery, all, fork } from "redux-saga/effects";import { push } from "connected-react-router";import { POSTS_LOADING_FAILURE, POSTS_LOADING_SUCCESS, POSTS_LOADING_REQUEST, POST_UPLOADING_SUCCESS, POST_UPLOADING_FAILURE, POST_UPLOADING_REQUEST, POST_DETAIL_LOADING_SUCCESS, POST_DETAIL_LOADING_FAILURE, POST_DETAIL_LOADING_REQUEST, POST_DELETE_SUCCESS, POST_DELETE_FAILURE, POST_DELETE_REQUEST, POST_EDIT_LOADING_SUCCESS, POST_EDIT_LOADING_FAILURE, POST_EDIT_UPLOADING_SUCCESS, POST_EDIT_UPLOADING_FAILURE, POST_EDIT_UPLOADING_REQUEST, POST_EDIT_LOADING_REQUEST, CATEGORY_FIND_FAILURE, CATEGORY_FIND_SUCCESS, CATEGORY_FIND_REQUEST, SEARCH_SUCCESS, SEARCH_FAILURE, SEARCH_REQUEST,} from "../types";// All Posts loadconst loadPostAPI = (payload) => { return axios.get(`/api/post/skip/${payload}`);};function* loadPosts(action) { try { const result = yield call(loadPostAPI, action.payload); console.log(result, "loadPosts"); yield put({ type: POSTS_LOADING_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: POSTS_LOADING_FAILURE, payload: e, }); }}function* watchLoadPosts() { yield takeEvery(POSTS_LOADING_REQUEST, loadPosts);}// Post Uploadconst uploadPostAPI = (payload) => { const config = { headers: { "Content-Type": "application/json", }, }; const token = payload.token; if (token) { config.headers["x-auth-token"] = token; } return axios.post("/api/post", payload, config);};function* uploadPosts(action) { try { console.log(action, "uploadPost function"); const result = yield call(uploadPostAPI, action.payload); console.log(result, "uploadPostAPI, action.payload"); yield put({ type: POST_UPLOADING_SUCCESS, payload: result.data, }); yield put(push(`/post/${result.data._id}`)); } catch (e) { yield put({ type: POST_UPLOADING_FAILURE, payload: e, }); yield put(push("/")); }}function* watchuploadPosts() { yield takeEvery(POST_UPLOADING_REQUEST, uploadPosts);}// Post Detailconst loadPostDetailAPI = (payload) => { console.log(payload); return axios.get(`/api/post/${payload}`);};function* loadPostDetail(action) { try { console.log(action); const result = yield call(loadPostDetailAPI, action.payload); console.log(result, "post_detail_saga_data"); yield put({ type: POST_DETAIL_LOADING_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: POST_DETAIL_LOADING_FAILURE, payload: e, }); yield put(push("/")); }}function* watchloadPostDetail() { yield takeEvery(POST_DETAIL_LOADING_REQUEST, loadPostDetail);}// Post Deleteconst DeletePostAPI = (payload) => { const config = { headers: { "Content-Type": "application/json", }, }; const token = payload.token; if (token) { config.headers["x-auth-token"] = token; } return axios.delete(`/api/post/${payload.id}`, config);};function* DeletePost(action) { try { const result = yield call(DeletePostAPI, action.payload); yield put({ type: POST_DELETE_SUCCESS, payload: result.data, }); yield put(push("/")); } catch (e) { yield put({ type: POST_DELETE_FAILURE, payload: e, }); }}function* watchDeletePost() { yield takeEvery(POST_DELETE_REQUEST, DeletePost);}// Post Edit Loadconst PostEditLoadAPI = (payload) => { const config = { headers: { "Content-Type": "application/json", }, }; const token = payload.token; if (token) { config.headers["x-auth-token"] = token; } return axios.get(`/api/post/${payload.id}/edit`, config);};function* PostEditLoad(action) { try { const result = yield call(PostEditLoadAPI, action.payload); yield put({ type: POST_EDIT_LOADING_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: POST_EDIT_LOADING_FAILURE, payload: e, }); yield put(push("/")); }}function* watchPostEditLoad() { yield takeEvery(POST_EDIT_LOADING_REQUEST, PostEditLoad);}// Post Edit UpLoadconst PostEditUploadAPI = (payload) => { const config = { headers: { "Content-Type": "application/json", }, }; const token = payload.token; if (token) { config.headers["x-auth-token"] = token; } return axios.post(`/api/post/${payload.id}/edit`, payload, config);};function* PostEditUpload(action) { try { const result = yield call(PostEditUploadAPI, action.payload); yield put({ type: POST_EDIT_UPLOADING_SUCCESS, payload: result.data, }); yield put(push(`/post/${result.data._id}`)); } catch (e) { yield put({ type: POST_EDIT_UPLOADING_FAILURE, payload: e, }); }}function* watchPostEditUpload() { yield takeEvery(POST_EDIT_UPLOADING_REQUEST, PostEditUpload);}// Category Findconst CategoryFindAPI = (payload) => { return axios.get(`/api/post/category/${encodeURIComponent(payload)}`);};function* CategoryFind(action) { try { const result = yield call(CategoryFindAPI, action.payload); yield put({ type: CATEGORY_FIND_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: CATEGORY_FIND_FAILURE, payload: e, }); }}function* watchCategoryFind() { yield takeEvery(CATEGORY_FIND_REQUEST, CategoryFind);}// Search Findconst SearchResultAPI = (payload) => { return axios.get(`/api/search/${encodeURIComponent(payload)}`);};function* SearchResult(action) { try { const result = yield call(SearchResultAPI, action.payload); yield put({ type: SEARCH_SUCCESS, payload: result.data, }); yield put(push(`/search/${encodeURIComponent(action.payload)}`)); } catch (e) { yield put({ type: SEARCH_FAILURE, payload: e, }); yield put(push("/")); }}function* watchSearchResult() { yield takeEvery(SEARCH_REQUEST, SearchResult);}export default function* postSaga() { yield all([ fork(watchLoadPosts), fork(watchuploadPosts), fork(watchloadPostDetail), fork(watchDeletePost), fork(watchPostEditLoad), fork(watchPostEditUpload), fork(watchCategoryFind), fork(watchSearchResult), ]);} import React, { Fragment } from "react";import { Row, Spinner } from "reactstrap";export const GrowingSpinner = ( <Fragment> <Row className="d-flex justify-content-center m-5"> <Spinner style={{ width: "2rem", height: "2rem" }} type="grow" color="primary" /> <Spinner style={{ width: "2rem", height: "2rem" }} type="grow" color="secondary" /> <Spinner style={{ width: "2rem", height: "2rem" }} type="grow" color="success" /> <Spinner style={{ width: "2rem", height: "2rem" }} type="grow" color="danger" /> <Spinner style={{ width: "2rem", height: "2rem" }} type="grow" color="warning" /> <Spinner style={{ width: "2rem", height: "2rem" }} type="grow" color="info" /> <Spinner style={{ width: "2rem", height: "2rem" }} type="grow" color="light" /> <Spinner style={{ width: "2rem", height: "2rem" }} type="grow" color="dark" /> </Row> </Fragment>); import React, { Fragment } from "react";import { Card, CardImg, CardBody, CardTitle, Button, Badge, Row,} from "reactstrap";import { Link } from "react-router-dom";import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";import { faMouse } from "@fortawesome/free-solid-svg-icons";const PostCardOne = ({ posts }) => { return ( <Fragment> {Array.isArray(posts) ? posts.map(({ _id, title, fileUrl, comments, views }) => { return ( <div key={_id} className="col-md-4"> <Link to={`/post/${_id}`} className="text-dark text-decoration-none" > <Card className="mb-3"> <CardImg top alt="카드이미지" src={fileUrl} /> <CardBody> <CardTitle className="text-truncate d-flex justify-content-between"> <span className="text-truncate">{title} </span> <span> <FontAwesomeIcon icon={faMouse} /> &nbsp;&nbsp; <span>{views}</span> </span> </CardTitle> <Row> <Button color="primary" className="p-2 btn-block"> More <Badge color="light">{comments.length}</Badge> </Button> </Row> </CardBody> </Card> </Link> </div> ); }) : ""} </Fragment> );};export default PostCardOne;

0

리엑트 블로그 만들기 14 - Post Card List 만들기

리엑트 블로그 만들기 15 - Post Card List 만들기 2 리엑트 블로그 만들기 14 - Post Card List 만들기 리엑트 블로그 만들기 13 - 라우트 리엑트 블로그 만들기 12 - Loading 만들기 리엑트 블로그 만들기 11 - 회원가입 리덕스 작업 리엑트 블로그 만들기 10 - 회원가입 2 리엑트 블로그 만들기 9 - 회원가입 리엑트 블로그 만들기 8 - 로그인 & 로그아웃 리엑트 블로그 만들기 7 - Login Modal 만들기 2 리엑트 블로그 만들기 6 - Login Modal 만들기 1 리엑트 블로그 만들기 5 - 리덕스 세팅하기 리엑트 블로그 만들기 4 - Navbar 작성하기 리엑트 블로그 만들기 3 - Header 작성하기 리엑트 블로그 만들기 2 - Footer 작성하기 리엑트 블로그 만들기 1 - 초기 setting 하기 리엑트 블로그 만들기 14 - Post Card List 만들기import { POSTS_LOADING_FAILURE, POSTS_LOADING_REQUEST, POSTS_LOADING_SUCCESS } from "../types";const initialState = { isAuthenticated: null, posts: [], postDetails: "", postCount: "", loading: false, error: "", creatorId: "", categoryFindResult: "", title: "", searchBy: "", searchResult: "",};export default function postReducer(state = initialState, action) { switch (action.type) { case POSTS_LOADING_REQUEST: return { ...state, posts: [], loading: true, } case POSTS_LOADING_SUCCESS: return { ...state, posts: [...state.posts, ...action.payload], loading: false, }; case POSTS_LOADING_FAILURE: return { ...state, loading: false, }; default: return state; };} // POST WRITEexport const POSTS_WRITE_REQUEST = "POST_WRITE_REQUEST";export const POSTS_WRITE_FAILURE = "POST_WRITE_FAILURE";export const POSTS_WRITE_SUCCESS = "POST_WRITE_SUCCESS";// POST LOADINGexport const POSTS_LOADING_REQUEST = "POST_LOADING_REQUEST";export const POSTS_LOADING_FAILURE = "POST_LOADING_FAILURE";export const POSTS_LOADING_SUCCESS = "POST_LOADING_SUCCESS"; import { combineReducers } from "redux";import { connectRouter } from "connected-react-router";import authReducer from "./authReducer.js";import postReducer from "./postReducer.js";import commentReducer from "./commentReducer.js";const createRootReducer = (history) => combineReducers({ router: connectRouter(history), auth: authReducer, post: postReducer, comment: commentReducer, });export default createRootReducer; import axios from "axios";import { put, call, takeEvery, all, fork } from "redux-saga/effects";import { push } from "connected-react-router";import { POSTS_LOADING_FAILURE, POSTS_LOADING_SUCCESS, POSTS_LOADING_REQUEST, POST_UPLOADING_SUCCESS, POST_UPLOADING_FAILURE, POST_UPLOADING_REQUEST, POST_DETAIL_LOADING_SUCCESS, POST_DETAIL_LOADING_FAILURE, POST_DETAIL_LOADING_REQUEST, POST_DELETE_SUCCESS, POST_DELETE_FAILURE, POST_DELETE_REQUEST, POST_EDIT_LOADING_SUCCESS, POST_EDIT_LOADING_FAILURE, POST_EDIT_UPLOADING_SUCCESS, POST_EDIT_UPLOADING_FAILURE, POST_EDIT_UPLOADING_REQUEST, POST_EDIT_LOADING_REQUEST, CATEGORY_FIND_FAILURE, CATEGORY_FIND_SUCCESS, CATEGORY_FIND_REQUEST, SEARCH_SUCCESS, SEARCH_FAILURE, SEARCH_REQUEST,} from "../types";// All Posts loadconst loadPostAPI = (payload) => { return axios.get(`/api/post/skip/${payload}`);};function* loadPosts(action) { try { const result = yield call(loadPostAPI, action.payload); console.log(result, "loadPosts"); yield put({ type: POSTS_LOADING_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: POSTS_LOADING_FAILURE, payload: e, }); }}function* watchLoadPosts() { yield takeEvery(POSTS_LOADING_REQUEST, loadPosts);}// Post Uploadconst uploadPostAPI = (payload) => { const config = { headers: { "Content-Type": "application/json", }, }; const token = payload.token; if (token) { config.headers["x-auth-token"] = token; } return axios.post("/api/post", payload, config);};function* uploadPosts(action) { try { console.log(action, "uploadPost function"); const result = yield call(uploadPostAPI, action.payload); console.log(result, "uploadPostAPI, action.payload"); yield put({ type: POST_UPLOADING_SUCCESS, payload: result.data, }); yield put(push(`/post/${result.data._id}`)); } catch (e) { yield put({ type: POST_UPLOADING_FAILURE, payload: e, }); yield put(push("/")); }}function* watchuploadPosts() { yield takeEvery(POST_UPLOADING_REQUEST, uploadPosts);}// Post Detailconst loadPostDetailAPI = (payload) => { console.log(payload); return axios.get(`/api/post/${payload}`);};function* loadPostDetail(action) { try { console.log(action); const result = yield call(loadPostDetailAPI, action.payload); console.log(result, "post_detail_saga_data"); yield put({ type: POST_DETAIL_LOADING_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: POST_DETAIL_LOADING_FAILURE, payload: e, }); yield put(push("/")); }}function* watchloadPostDetail() { yield takeEvery(POST_DETAIL_LOADING_REQUEST, loadPostDetail);}// Post Deleteconst DeletePostAPI = (payload) => { const config = { headers: { "Content-Type": "application/json", }, }; const token = payload.token; if (token) { config.headers["x-auth-token"] = token; } return axios.delete(`/api/post/${payload.id}`, config);};function* DeletePost(action) { try { const result = yield call(DeletePostAPI, action.payload); yield put({ type: POST_DELETE_SUCCESS, payload: result.data, }); yield put(push("/")); } catch (e) { yield put({ type: POST_DELETE_FAILURE, payload: e, }); }}function* watchDeletePost() { yield takeEvery(POST_DELETE_REQUEST, DeletePost);}// Post Edit Loadconst PostEditLoadAPI = (payload) => { const config = { headers: { "Content-Type": "application/json", }, }; const token = payload.token; if (token) { config.headers["x-auth-token"] = token; } return axios.get(`/api/post/${payload.id}/edit`, config);};function* PostEditLoad(action) { try { const result = yield call(PostEditLoadAPI, action.payload); yield put({ type: POST_EDIT_LOADING_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: POST_EDIT_LOADING_FAILURE, payload: e, }); yield put(push("/")); }}function* watchPostEditLoad() { yield takeEvery(POST_EDIT_LOADING_REQUEST, PostEditLoad);}// Post Edit UpLoadconst PostEditUploadAPI = (payload) => { const config = { headers: { "Content-Type": "application/json", }, }; const token = payload.token; if (token) { config.headers["x-auth-token"] = token; } return axios.post(`/api/post/${payload.id}/edit`, payload, config);};function* PostEditUpload(action) { try { const result = yield call(PostEditUploadAPI, action.payload); yield put({ type: POST_EDIT_UPLOADING_SUCCESS, payload: result.data, }); yield put(push(`/post/${result.data._id}`)); } catch (e) { yield put({ type: POST_EDIT_UPLOADING_FAILURE, payload: e, }); }}function* watchPostEditUpload() { yield takeEvery(POST_EDIT_UPLOADING_REQUEST, PostEditUpload);}// Category Findconst CategoryFindAPI = (payload) => { return axios.get(`/api/post/category/${encodeURIComponent(payload)}`);};function* CategoryFind(action) { try { const result = yield call(CategoryFindAPI, action.payload); yield put({ type: CATEGORY_FIND_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: CATEGORY_FIND_FAILURE, payload: e, }); }}function* watchCategoryFind() { yield takeEvery(CATEGORY_FIND_REQUEST, CategoryFind);}// Search Findconst SearchResultAPI = (payload) => { return axios.get(`/api/search/${encodeURIComponent(payload)}`);};function* SearchResult(action) { try { const result = yield call(SearchResultAPI, action.payload); yield put({ type: SEARCH_SUCCESS, payload: result.data, }); yield put(push(`/search/${encodeURIComponent(action.payload)}`)); } catch (e) { yield put({ type: SEARCH_FAILURE, payload: e, }); yield put(push("/")); }}function* watchSearchResult() { yield takeEvery(SEARCH_REQUEST, SearchResult);}export default function* postSaga() { yield all([ fork(watchLoadPosts), fork(watchuploadPosts), fork(watchloadPostDetail), fork(watchDeletePost), fork(watchPostEditLoad), fork(watchPostEditUpload), fork(watchCategoryFind), fork(watchSearchResult), ]);}

0

리엑트 블로그 만들기 13 - 라우트

리엑트 블로그 만들기 15 - Post Card List 만들기 2 리엑트 블로그 만들기 14 - Post Card List 만들기 리엑트 블로그 만들기 13 - 라우트 리엑트 블로그 만들기 12 - Loading 만들기 리엑트 블로그 만들기 11 - 회원가입 리덕스 작업 리엑트 블로그 만들기 10 - 회원가입 2 리엑트 블로그 만들기 9 - 회원가입 리엑트 블로그 만들기 8 - 로그인 & 로그아웃 리엑트 블로그 만들기 7 - Login Modal 만들기 2 리엑트 블로그 만들기 6 - Login Modal 만들기 1 리엑트 블로그 만들기 5 - 리덕스 세팅하기 리엑트 블로그 만들기 4 - Navbar 작성하기 리엑트 블로그 만들기 3 - Header 작성하기 리엑트 블로그 만들기 2 - Footer 작성하기 리엑트 블로그 만들기 1 - 초기 setting 하기 리엑트 블로그 만들기 13 - 라우트import React from 'react';const CategoryResult = () => { return <h1>CategoryResult</h1>}export default CategoryResult; import { Fragment, useEffect } from "react";import { Helmet } from "react-helmet";import { useDispatch, useSelector } from "react-redux";import { Row, Spinner } from 'reactstrap';import PostCardOne from "../../components/post/PostCardOne";import { POSTS_LOADING_REQUEST } from "../../redux/types";const PostCardList = () => { const { posts } = useSelector((state) => state.post) const dispatch = useDispatch() useEffect(() => { dispatch({ type: POSTS_LOADING_REQUEST }) }, [dispatch]) return ( <Fragment> <Helmet title="Home" /> <Row> {posts ? <PostCardOne posts={posts} /> : <Spinner />} </Row> </Fragment> )}export default PostCardList; import React from 'react';const PostDetail = () => { return ( <div> PostDetail </div> )}export default PostDetail; import React from 'react';const PostEdit = () => { return ( <div> PostEdit </div> )}export default PostEdit; import React, { useState } from 'react'import { useDispatch, useSelector } from 'react-redux'import { Col, Form, FormGroup, Label, Progress, Button, Input } from 'reactstrap'import { CKEditor } from "@ckeditor/ckeditor5-react"import ClassicEditor from '@ckeditor/ckeditor5-editor-classic/src/classiceditor'import { editorConfiguration } from '../../components/editor/EditorConfig'import Myinit from '../../components/editor/UploadAdapter'const PostWrite = () => { const { isAuthenticated } = useSelector((state) => state.auth) const [form, setValues] = useState({ title: "", contents: "", fileUrl: "" }) const diapatch = useDispatch const onChange = (e) => { setValues({ ...form, [e.target.name]: e.target.value }) } const onSubmit = async (e) => { await e.preventDefault() const { title, contents, fileUrl, category } = form } const getDataFromCKEditor = (event, editor) => { console.log("editor") } return ( <div> {isAuthenticated ? ( <Form onSubmit={onSubmit}> <FormGroup className="mb-3"> <Label for="title">Title</Label> <Input type="text" name="title" id="title" className="form-control" onChange={onChange} /> </FormGroup> <FormGroup className="mb-3"> <Label for="category">Category</Label> <Input type="text" name="category" id="category" className="form-control" onChange={onChange} /> </FormGroup> <FormGroup className="mb-3"> <Label for="content">Content</Label> <CKEditor editor={ClassicEditor} config={editorConfiguration} onInit={Myinit} onBlur={getDataFromCKEditor} /> <Button color="success" block className="mt-3 col-md-2 offset-md-10 mb-3" > 제출하기 </Button> </FormGroup> </Form> ) : ( <Col width={50} className="p-5 m-5"> <Progress animated color="info" value={100} /> </Col> )} </div> )}export default PostWrite import React from 'react'const Profile = () => { <div> Profile </div>}export default Profile import React from 'react'const Search = () => { return ( <div> Search </div> )}export default Search

0

리엑트 블로그 만들기 12 - Loading 만들기

리엑트 블로그 만들기 15 - Post Card List 만들기 2 리엑트 블로그 만들기 14 - Post Card List 만들기 리엑트 블로그 만들기 13 - 라우트 리엑트 블로그 만들기 12 - Loading 만들기 리엑트 블로그 만들기 11 - 회원가입 리덕스 작업 리엑트 블로그 만들기 10 - 회원가입 2 리엑트 블로그 만들기 9 - 회원가입 리엑트 블로그 만들기 8 - 로그인 & 로그아웃 리엑트 블로그 만들기 7 - Login Modal 만들기 2 리엑트 블로그 만들기 6 - Login Modal 만들기 1 리엑트 블로그 만들기 5 - 리덕스 세팅하기 리엑트 블로그 만들기 4 - Navbar 작성하기 리엑트 블로그 만들기 3 - Header 작성하기 리엑트 블로그 만들기 2 - Footer 작성하기 리엑트 블로그 만들기 1 - 초기 setting 하기 리엑트 블로그 만들기 12 - Loading 만들기// USER LOADINGexport const USER_LOADING_REQUEST = "USER_LOADING_REQUEST"export const USER_LOADING_SUCCESS = "USER_LOADING_SUCCESS"export const USER_LOADING_FAILURE = "USER_LOADING_FAILURE" const authReducer = (state = initialState, action) => { switch (action.type) { case REGISTER_REQUEST: case LOGIN_REQUEST: case LOGOUT_REQUEST: return { ...state, errorMsg: "", isLoading: true } case REGISTER_SUCCESS: case LOGIN_SUCCESS: localStorage.setItem("token", action.payload.token) return { ...state, ...action.payload, isAuthenticated: true, isLoading: false, userId: action.payload.user.userId, userRole: action.payload.user.role, errorMsg: "" } case REGISTER_FAILURE: case LOGOUT_FAILURE: case LOGIN_FAILURE: localStorage.removeItem("token") return { ...state, ...action.payload, token: null, user: null, userId: null, isAuthenticated: false, isLoading: false, userRole: null, errorMsg: action.payload.user.role } case LOGOUT_SUCCESS: localStorage.removeItem("token") return { token: null, user: null, userId: null, isAuthenticated: false, isLoading: false, userRole: null, errorMsg: "" }; case CLEAR_ERROR_REQUEST: return { ...state, errorMsg: null, } case CLEAR_ERROR_SUCCESS: return { ...state, errorMsg: null, } case CLEAR_ERROR_FAILURE: return { ...state, errorMsg: null } case USER_LOADING_REQUEST: return { ...state, isLoading: true } case USER_LOADING_SUCCESS: return { ...state, isAuthenticated: true, isLoading: false, user: action.payload, userId: action.payload._id, userName: action.payload.userName, userRole: action.payload.role } case USER_LOADING_FAILURE: return { ...state, user: null, isAuthenticated: false, isLoading: false, userRole: "", } default: return state; }} // User Loadingconst userLoadingAPI = (token) => { console.log(token); const config = { headers: { "Content-Type": "applicatio n/json", }, }; if (token) { config.headers["x-auth-token"] = token; } return axios.get("api/auth/user", config);}function* userLoading(action) { try { console.log(action, "userLoading") const result = yield call(userLoadingAPI, action.payload) console.log(result) yield put({ type: USER_LOADING_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: USER_LOADING_FAILURE, payload: e.response }) }} // Clear Errorfunction* clearError() { try { yield put({ type: CLEAR_ERROR_SUCCESS, }); } catch (e) { yield put({ type: CLEAR_ERROR_FAILURE, }); }}function* watchClearError() { yield takeEvery(CLEAR_ERROR_REQUEST, clearError);} export default function* authSaga() { yield all([ fork(watchLoginUser), fork(watchLogoutUser), fork(watchUserLoading), fork(watchRegisterUser), fork(watchClearError), ]);} import { USER_LOADING_REQUEST } from '../../redux/types';import store from '../../store';const laodUser = () => { try { store.dispatch({ type: USER_LOADING_REQUEST, payload: localStorage.getItem("token"), }); } catch (e) { console.log(e); }}export default laodUser; import React from 'react';import ReactDOM from 'react-dom';import App from './App';import loadUser from './components/auth/loadUser';import './index.css';import reportWebVitals from './reportWebVitals';// 로그인을 항상 유지loadUser();ReactDOM.render( <App />, document.getElementById('root'));// If you want to start measuring performance in your app, pass a function// to log results (for example: reportWebVitals(console.log))// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitalsreportWebVitals();

0

리엑트 블로그 만들기 11 - 회원가입 리덕스 작업

리엑트 블로그 만들기 15 - Post Card List 만들기 2 리엑트 블로그 만들기 14 - Post Card List 만들기 리엑트 블로그 만들기 13 - 라우트 리엑트 블로그 만들기 12 - Loading 만들기 리엑트 블로그 만들기 11 - 회원가입 리덕스 작업 리엑트 블로그 만들기 10 - 회원가입 2 리엑트 블로그 만들기 9 - 회원가입 리엑트 블로그 만들기 8 - 로그인 & 로그아웃 리엑트 블로그 만들기 7 - Login Modal 만들기 2 리엑트 블로그 만들기 6 - Login Modal 만들기 1 리엑트 블로그 만들기 5 - 리덕스 세팅하기 리엑트 블로그 만들기 4 - Navbar 작성하기 리엑트 블로그 만들기 3 - Header 작성하기 리엑트 블로그 만들기 2 - Footer 작성하기 리엑트 블로그 만들기 1 - 초기 setting 하기 리엑트 블로그 만들기 11 - 회원가입 리덕스 작업const authReducer = (state = initialState, action) => { switch (action.type) { case REGISTER_REQUEST: case LOGIN_REQUEST: case LOGOUT_REQUEST: return { ...state, errorMsg: "", isLoading: true } case REGISTER_SUCCESS: case LOGIN_SUCCESS: localStorage.setItem("token", action.payload.token) return { ...state, ...action.payload, isAuthenticated: true, isLoading: false, userId: action.payload.user.userId, userRole: action.payload.user.role, errorMsg: "" } case REGISTER_FAILURE: case LOGOUT_FAILURE: case LOGIN_FAILURE: localStorage.removeItem("token") return { ...state, ...action.payload, token: null, user: null, userId: null, isAuthenticated: false, isLoading: false, userRole: null, errorMsg: action.payload.user.role } case LOGOUT_SUCCESS: localStorage.removeItem("token") return { token: null, user: null, userId: null, isAuthenticated: false, isLoading: false, userRole: null, errorMsg: "" }; case CLEAR_ERROR_REQUEST: return { ...state, errorMsg: null, } case CLEAR_ERROR_SUCCESS: return { ...state, errorMsg: null, } case CLEAR_ERROR_FAILURE: return { ...state, errorMsg: null } case USER_LOADING_REQUEST: return { ...state, isLoading: true } case USER_LOADING_SUCCESS: return { ...state, isAuthenticated: true, isLoading: false, user: action.payload, userId: action.payload._id, userName: action.payload.userName, userRole: action.payload.role } case USER_LOADING_FAILURE: return { ...state, user: null, isAuthenticated: false, isLoading: false, userRole: "", } default: return state; }} // Registerconst registerUserAPI = (req) => { console.log(req, "req"); return axios.post("api/user", req);};function* registerUser(action) { try { const result = yield call(registerUserAPI, action.payload); console.log(result, "RegisterUser Data"); yield put({ type: REGISTER_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: REGISTER_FAILURE, payload: e.response, }); }}function* watchRegisterUser() { yield takeEvery(REGISTER_REQUEST, registerUser);} export default function* authSaga() { yield all([ fork(watchLoginUser), fork(watchLogoutUser), fork(watchUserLoading), fork(watchRegisterUser), fork(watchClearError), ]);}

0

리엑트 블로그 만들기 10 - 회원가입 2

리엑트 블로그 만들기 15 - Post Card List 만들기 2 리엑트 블로그 만들기 14 - Post Card List 만들기 리엑트 블로그 만들기 13 - 라우트 리엑트 블로그 만들기 12 - Loading 만들기 리엑트 블로그 만들기 11 - 회원가입 리덕스 작업 리엑트 블로그 만들기 10 - 회원가입 2 리엑트 블로그 만들기 9 - 회원가입 리엑트 블로그 만들기 8 - 로그인 & 로그아웃 리엑트 블로그 만들기 7 - Login Modal 만들기 2 리엑트 블로그 만들기 6 - Login Modal 만들기 1 리엑트 블로그 만들기 5 - 리덕스 세팅하기 리엑트 블로그 만들기 4 - Navbar 작성하기 리엑트 블로그 만들기 3 - Header 작성하기 리엑트 블로그 만들기 2 - Footer 작성하기 리엑트 블로그 만들기 1 - 초기 setting 하기 리엑트 블로그 만들기 10 - 회원가입 2import React, { Fragment, useCallback, useEffect, useState } from "react";import { useDispatch, useSelector } from "react-redux";import { Link } from "react-router-dom";import { Button, Collapse, Container, Form, Nav, Navbar, NavbarToggler, NavItem,} from "reactstrap";import { LOGOUT_REQUEST } from "../redux/types";import LoginModal from "./auth/LoginModal";import RegisterModal from "./auth/RegisterModal";const AppNavbar = () => { const [isOpen, setIsOpen] = useState(false); const { isAuthenticated, user, userRole } = useSelector( (state) => state.auth ); console.log(userRole, "UserRole"); const dispatch = useDispatch(); const onLogout = useCallback(() => { dispatch({ type: LOGOUT_REQUEST, }); }, [dispatch]); useEffect(() => { setIsOpen(false); }, [user]); const handleToggle = () => { setIsOpen(!isOpen); }; const addPostClick = () => {}; const authLink = ( <Fragment> <NavItem> {userRole === "MainJuin" ? ( <Form className="col mt-2"> <Link to="posts" className="btn btn-success block text-white px-3" onClick={addPostClick} > AddPost </Link> </Form> ) : ( "" )} </NavItem> <NavItem className="d-flex justify-content-center"> <Form className="col mt-2"> {user && user.name ? ( <Link> <Button outline color="light" className="px-3" block> <strong>{user ? `Welcom ${user.name}` : ""}</strong> </Button> </Link> ) : ( <Button outline color="light" className="px-3" block> <strong>No User</strong> </Button> )} </Form> </NavItem> <NavItem> <Form className="col"> <Link onClick={onLogout} to="#"> <Button outline color="light" className="mt-2" block> Logout </Button> </Link> </Form> </NavItem> </Fragment> ); const guestLink = ( <Fragment> <NavItem> <RegisterModal /> </NavItem> <NavItem> <LoginModal /> </NavItem> </Fragment> ); return ( <div> <Navbar color="dark" dark expand="lg" className="sticky-top"> <Container> <Link to="/" className="text-white text-decoration-none"> Victor의 블로그 </Link> <NavbarToggler onClick={handleToggle} /> <Collapse isOpen={isOpen} navbar> <Nav className="ml-auto d-flex justify-content-around" navbar> {isAuthenticated ? authLink : guestLink} </Nav> </Collapse> </Container> </Navbar> </div> );};export default AppNavbar;

0

리엑트 블로그 만들기 9 - 회원가입

리엑트 블로그 만들기 15 - Post Card List 만들기 2 리엑트 블로그 만들기 14 - Post Card List 만들기 리엑트 블로그 만들기 13 - 라우트 리엑트 블로그 만들기 12 - Loading 만들기 리엑트 블로그 만들기 11 - 회원가입 리덕스 작업 리엑트 블로그 만들기 10 - 회원가입 2 리엑트 블로그 만들기 9 - 회원가입 리엑트 블로그 만들기 8 - 로그인 & 로그아웃 리엑트 블로그 만들기 7 - Login Modal 만들기 2 리엑트 블로그 만들기 6 - Login Modal 만들기 1 리엑트 블로그 만들기 5 - 리덕스 세팅하기 리엑트 블로그 만들기 4 - Navbar 작성하기 리엑트 블로그 만들기 3 - Header 작성하기 리엑트 블로그 만들기 2 - Footer 작성하기 리엑트 블로그 만들기 1 - 초기 setting 하기 리엑트 블로그 만들기 9 - 회원가입// REGISTERexport const REGISTER_REQUEST = "REGISTER_REQUEST";export const REGISTER_SUCCESS = "REGISTER_SUCCESS";export const REGISTER_FAILURE = "REGISTER_FAILURE"; import React, { useEffect, useState } from "react";import { useDispatch, useSelector } from "react-redux";import { Alert, Button, Form, FormGroup, Input, Label, Modal, ModalBody, ModalHeader, NavLink,} from "reactstrap";import { CLEAR_ERROR_REQUEST, REGISTER_REQUEST } from "../../redux/types";const RegisterModal = () => { const [modal, setModal] = useState(false); const [form, setValue] = useState({ name: "", email: "", password: "", }); const [localMsg, setLocalMsg] = useState(""); const { errorMsg } = useSelector((state) => state.auth); const dispatch = useDispatch(); const handleToggle = () => { dispatch({ type: CLEAR_ERROR_REQUEST, }); setModal(!modal); }; useEffect(() => { try { setLocalMsg(errorMsg); } catch (e) { console.error(e); } }, [errorMsg]); const onChange = (e) => { setValue({ ...form, [e.target.name]: e.target.value, }); }; const onSubmit = (e) => { e.preventDefault(); const { name, email, password } = form; const newUser = { name, email, password }; console.log(newUser, "newUser"); dispatch({ type: REGISTER_REQUEST, payload: newUser, }); }; return ( <div> <NavLink onClick={handleToggle} href="#"> Register </NavLink> <Modal isOpen={modal} toggle={handleToggle}> <ModalHeader toggle={handleToggle}>Register</ModalHeader> <ModalBody> {localMsg ? <Alert color="danger"></Alert> : null} <Form onSubmit={onSubmit}> <FormGroup> <Label for="name">Name</Label> <Input type="text" name="name" id="name" placeholder="Name" onChange={onChange} /> <Label for="email">Email</Label> <Input type="text" name="email" id="email" placeholder="email" onChange={onChange} /> <Label for="password">Password</Label> <Input type="text" name="password" id="password" placeholder="Password" onChange={onChange} /> <Button color="dark" className="mt-2" block> Register </Button> </FormGroup> </Form> </ModalBody> </Modal> </div> );};export default RegisterModal; import React, { Fragment, useCallback, useEffect, useState } from "react";import { useDispatch, useSelector } from "react-redux";import { Link } from "react-router-dom";import { Button, Collapse, Container, Form, Nav, Navbar, NavbarToggler, NavItem,} from "reactstrap";import { LOGOUT_REQUEST } from "../redux/types";import LoginModal from "./auth/LoginModal";import RegisterModal from "./auth/RegisterModal";const AppNavbar = () => { const [isOpen, setIsOpen] = useState(false); const { isAuthenticated, user, userRole } = useSelector( (state) => state.auth ); console.log(userRole, "UserRole"); const dispatch = useDispatch(); const onLogout = useCallback(() => { dispatch({ type: LOGOUT_REQUEST, }); }, [dispatch]); useEffect(() => { setIsOpen(false); }, [user]); const handleToggle = () => { setIsOpen(!isOpen); }; const addPostClick = () => {}; const authLink = ( <Fragment> <NavItem> {userRole === "MainJuin" ? ( <Form className="col mt-2"> <Link to="posts" className="btn btn-success block text-white px-3" onClick={addPostClick} > AddPost </Link> </Form> ) : ( "" )} </NavItem> <NavItem className="d-flex justify-content-center"> <Form className="col mt-2"> {user && user.name ? ( <Link> <Button outline color="light" className="px-3" block> <strong>{user ? `Welcom ${user.name}` : ""}</strong> </Button> </Link> ) : ( <Button outline color="light" className="px-3" block> <strong>No User</strong> </Button> )} </Form> </NavItem> <NavItem> <Form className="col"> <Link onClick={onLogout} to="#"> <Button outline color="light" className="mt-2" block> Logout </Button> </Link> </Form> </NavItem> </Fragment> ); const guestLink = ( <Fragment> <NavItem> <RegisterModal /> </NavItem> <NavItem> <LoginModal /> </NavItem> </Fragment> ); return ( <div> <Navbar color="dark" dark expand="lg" className="sticky-top"> <Container> <Link to="/" className="text-white text-decoration-none"> Victor의 블로그 </Link> <NavbarToggler onClick={handleToggle} /> <Collapse isOpen={isOpen} navbar> <Nav className="ml-auto d-flex justify-content-around" navbar> {isAuthenticated ? authLink : guestLink} </Nav> </Collapse> </Container> </Navbar> </div> );};export default AppNavbar;

0

리엑트 블로그 만들기 8 - 로그인 & 로그아웃

리엑트 블로그 만들기 15 - Post Card List 만들기 2 리엑트 블로그 만들기 14 - Post Card List 만들기 리엑트 블로그 만들기 13 - 라우트 리엑트 블로그 만들기 12 - Loading 만들기 리엑트 블로그 만들기 11 - 회원가입 리덕스 작업 리엑트 블로그 만들기 10 - 회원가입 2 리엑트 블로그 만들기 9 - 회원가입 리엑트 블로그 만들기 8 - 로그인 & 로그아웃 리엑트 블로그 만들기 7 - Login Modal 만들기 2 리엑트 블로그 만들기 6 - Login Modal 만들기 1 리엑트 블로그 만들기 5 - 리덕스 세팅하기 리엑트 블로그 만들기 4 - Navbar 작성하기 리엑트 블로그 만들기 3 - Header 작성하기 리엑트 블로그 만들기 2 - Footer 작성하기 리엑트 블로그 만들기 1 - 초기 setting 하기 리엑트 블로그 만들기 8 - 로그인 & 로그아웃//LOGINexport const LOGIN_REQUEST = "LOGIN_REQUEST";export const LOGIN_SUCCESS = "LOGIN_SUCCESS";export const LOGIN_FAILURE = "LOGIN_FAILURE";//LOGINexport const LOGOUT_REQUEST = "LOGOUT_REQUEST";export const LOGOUT_SUCCESS = "LOGOUT_SUCCESS";export const LOGOUT_FAILURE = "LOGOUT_FAILURE";// CLEAR ERRORexport const CLEAR_ERROR_REQUEST = "CLEAR_REQUEST";export const CLEAR_ERROR_SUCCESS = "CLEAR_SUCCESS";export const CLEAR_ERROR_FAILURE = "CLEAR_FAILURE"; import { LOGIN_REQUEST, LOGIN_SUCCESS, LOGIN_FAILURE, CLEAR_ERROR_REQUEST, CLEAR_ERROR_SUCCESS, CLEAR_ERROR_FAILURE, LOGOUT_REQUEST, LOGOUT_SUCCESS, LOGOUT_FAILURE,} from "../types";const initialState = { token: localStorage.getItem("token"), isAuthenticated: null, isLoading: false, user: "", userId: "", userName: "", userRole: "", errorMsg: "", successMsg: "",};const authReducer = (state = initialState, action) => { switch (action.type) { case LOGOUT_REQUEST: case LOGIN_REQUEST: return { ...state, errorMsg: "", }; case LOGIN_SUCCESS: localStorage.setItem("token", action.payload.token); return { ...state, ...action.payload, isAuthenticated: true, isLoading: false, userId: action.payload.user.id, userRole: action.payload.user.role, errorMsg: "", }; case LOGOUT_FAILURE: case LOGIN_FAILURE: localStorage.removeItem("token"); return { ...state, ...action.payload, token: null, user: null, userId: null, userRole: null, errorMsg: action.payload.data.msg, }; case LOGOUT_SUCCESS: localStorage.removeItem("token"); return { ...state, ...action.payload, token: null, user: null, userId: null, isAuthenticated: false, isLoading: false, userRole: null, errorMsg: "", }; case CLEAR_ERROR_REQUEST: return { ...state, errorMsg: null, }; case CLEAR_ERROR_SUCCESS: return { ...state, errorMsg: null, }; case CLEAR_ERROR_FAILURE: return { ...state, errorMsg: null, }; default: return state; }};export default authReducer; import { all, put } from "@redux-saga/core/effects";import axios from "axios";import { call, fork, takeEvery } from "redux-saga/effects";import { LOGIN_FAILURE, LOGIN_REQUEST, LOGIN_SUCCESS, LOGOUT_REQUEST,} from "../types";const loginUserAPI = (loginData) => { console.log(loginData, "loginData"); const config = { headers: { "Content-Type": "application/json", }, }; return axios.post("api/auth", loginData, config);};function* loginUser(action) { try { const result = yield call(loginUserAPI, action.payload); console.log(result); yield put({ type: LOGIN_SUCCESS, payload: result.data, }); } catch (e) { yield put({ type: LOGIN_FAILURE, payload: e.response, }); }}function* logout(action) { try { const result = yield call(loginUserAPI, action.payload); console.log(result); yield put({ type: LOGIN_SUCCESS, }); } catch (e) { yield put({ type: LOGIN_FAILURE, }); console.log(e); }}function* watchLoginUser() { yield takeEvery(LOGIN_REQUEST, loginUser);}function* watchLogout() { yield takeEvery(LOGOUT_REQUEST, logout);}export default function* authSaga() { yield all([fork(watchLoginUser), fork(watchLogout)]);} import React, { useCallback, useEffect, useState } from "react";import { useDispatch, useSelector } from "react-redux";import { Link } from "react-router-dom";import { Collapse, Container, Nav, Navbar, NavbarToggler } from "reactstrap";import { LOGOUT_REQUEST } from "../redux/types";import LoginModal from "./auth/LoginModal";const AppNavbar = () => { const [isOpen, setIsOpen] = useState(false); const { isAuthenticated, user, userRole } = useSelector( (state) => state.auth ); console.log(userRole, "UserRole"); const dispatch = useDispatch(); const onLogout = useCallback(() => { dispatch({ type: LOGOUT_REQUEST, }); }, [dispatch]); useEffect(() => { setIsOpen(false); }, [user]); const handleToggle = () => { setIsOpen(!isOpen); }; return ( <div> <Navbar color="dark" dark expand="lg" className="sticky-top"> <Container> <Link to="/" className="text-white text-decoration-none"> Victor의 블로그 </Link> <NavbarToggler onClick={handleToggle} /> <Collapse isOpen={isOpen} navbar> <Nav className="ml-auto d-flex justify-content-around" navbar> {isAuthenticated ? ( <h1 className="text-white">authLink</h1> ) : ( <LoginModal /> )} </Nav> </Collapse> </Container> </Navbar> </div> );};export default AppNavbar;