Old Branch

스프링 부트(Spring Boot)와 Security, MySQL, React를 사용한 Spring Polling App(4)

woolbro 2019. 7. 19. 11:10
반응형

이번 포스팅은 스프링 부트 Polling App의 마지막 포스팅으로 그동안 작성했던 코드를 화면으로 띄워주도록 하겠습니다!!
(원본링크 https://www.callicoder.com/spring-boot-spring-security-jwt-mysql-react-app-part-1/)

 

본 포스팅을 따라하기 어렵거나, 결과물을 위해 빠르게 보셔야 하는 분을 위해 코드를 올려놓았습니다.

 

코드는 Github 저장소에 올려놓았습니다.

 

이전 포스팅은 여기 있습니다.

더보기

 

완성되고 실행이 정상적으로 이루어지면 아래와같이 실행됩니다 :)

 

 


 

Front-End 작성하기

설치 -> app 만들기 -> Dependencies 설치하기 -> Design 수정하기 -> 실행 의 과정이 있습니다!!

코드는 Github 저장소에 올려놓았습니다.

1. create-react-app 설치하기

npm install -g create-react-app

2. 사용자 app 만들기

create-react-app polling-app-client

3. Dependency 추가 설치하기

cd polling-app-client

npm install antd react-router-dom --save

npm install react-app-rewired babel-plugin-import react-app-rewire-less --save-dev

4. Ant 디자인 설정하기

package.json 안에 설정되어있던 react-scripts 를 react-app-rewired 로 바꾸어 주고 config-overrides.js 파일을 추가 해 줍니다.

 

package.json

{
  "name": "polling-app-client",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "antd": "^3.2.2",
    "react": "^16.5.2",
    "react-dom": "^16.5.2",
    "react-router-dom": "^4.3.1",
    "react-scripts": "1.1.5"
  },
  "scripts": {
    "start": "react-app-rewired start",
    "build": "react-app-rewired build",
    "test": "react-app-rewired test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "devDependencies": {
    "babel-plugin-import": "^1.6.5",
    "react-app-rewire-less": "^2.1.0",
    "react-app-rewired": "^1.4.1"
  }
}

 

config-overrides.js

const { injectBabelPlugin } = require('react-app-rewired');
const rewireLess = require('react-app-rewire-less');

module.exports = function override(config, env) {
    config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config);
    config = rewireLess.withLoaderOptions({
      modifyVars: { 
          "@layout-body-background": "#FFFFFF",
          "@layout-header-background": "#FFFFFF",
          "@layout-footer-background": "#FFFFFF" 
      },
      javascriptEnabled: true
    })(config, env);
    return config;
};

 

5. Running the App

npm start

 

 

Front-end 코드

코드는 Github 저장소에 올려놓았습니다.

 

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './app/App';
import registerServiceWorker from './registerServiceWorker';
import { BrowserRouter as Router } from 'react-router-dom';

ReactDOM.render(
    <Router>
        <App />
    </Router>, 
    document.getElementById('root')
);

registerServiceWorker();

 

src/app/App.js

import React, { Component } from 'react';
import './App.css';
import {
  Route,
  withRouter,
  Switch
} from 'react-router-dom';

import { getCurrentUser } from '../util/APIUtils';
import { ACCESS_TOKEN } from '../constants';

import PollList from '../poll/PollList';
import NewPoll from '../poll/NewPoll';
import Login from '../user/login/Login';
import Signup from '../user/signup/Signup';
import Profile from '../user/profile/Profile';
import AppHeader from '../common/AppHeader';
import NotFound from '../common/NotFound';
import LoadingIndicator from '../common/LoadingIndicator';
import PrivateRoute from '../common/PrivateRoute';

import { Layout, notification } from 'antd';
const { Content } = Layout;

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      currentUser: null,
      isAuthenticated: false,
      isLoading: false
    }
    this.handleLogout = this.handleLogout.bind(this);
    this.loadCurrentUser = this.loadCurrentUser.bind(this);
    this.handleLogin = this.handleLogin.bind(this);

    notification.config({
      placement: 'topRight',
      top: 70,
      duration: 3,
    });    
  }

  loadCurrentUser() {
    this.setState({
      isLoading: true
    });
    getCurrentUser()
    .then(response => {
      this.setState({
        currentUser: response,
        isAuthenticated: true,
        isLoading: false
      });
    }).catch(error => {
      this.setState({
        isLoading: false
      });  
    });
  }

  componentDidMount() {
    this.loadCurrentUser();
  }

  handleLogout(redirectTo="/", notificationType="success", description="You're successfully logged out.") {
    localStorage.removeItem(ACCESS_TOKEN);

    this.setState({
      currentUser: null,
      isAuthenticated: false
    });

    this.props.history.push(redirectTo);
    
    notification[notificationType]({
      message: 'Polling App',
      description: description,
    });
  }

  handleLogin() {
    notification.success({
      message: 'Polling App',
      description: "You're successfully logged in.",
    });
    this.loadCurrentUser();
    this.props.history.push("/");
  }

  render() {
    if(this.state.isLoading) {
      return <LoadingIndicator />
    }
    return (
        <Layout className="app-container">
          <AppHeader isAuthenticated={this.state.isAuthenticated} 
            currentUser={this.state.currentUser} 
            onLogout={this.handleLogout} />

          <Content className="app-content">
            <div className="container">
              <Switch>      
                <Route exact path="/" 
                  render={(props) => <PollList isAuthenticated={this.state.isAuthenticated} 
                      currentUser={this.state.currentUser} handleLogout={this.handleLogout} {...props} />}>
                </Route>
                <Route path="/login" 
                  render={(props) => <Login onLogin={this.handleLogin} {...props} />}></Route>
                <Route path="/signup" component={Signup}></Route>
                <Route path="/users/:username" 
                  render={(props) => <Profile isAuthenticated={this.state.isAuthenticated} currentUser={this.state.currentUser} {...props}  />}>
                </Route>
                <PrivateRoute authenticated={this.state.isAuthenticated} path="/poll/new" component={NewPoll} handleLogout={this.handleLogout}></PrivateRoute>
                <Route component={NotFound}></Route>
              </Switch>
            </div>
          </Content>
        </Layout>
    );
  }
}

export default withRouter(App);

 

src / common - 공통 구성 요소

  • AppHeader.js : 인증되지 않은 사용자에 대한 로그인 및 SignUp 버튼과 인증 된 사용자에 대한 홈, 프로필 및 폴링 버튼을 렌더링하는 헤더 구성 요소.

  • LoadingIndicator.js : API 호출이 진행되는 동안 다른 구성 요소에서 로딩 표시를 렌더링하는 데 사용됩니다.

  • NotFound.js : App현재 url과 일치하는 경로가 없으면 구성 요소에서 이것을 사용 하여 404 Not Found 페이지를 렌더링합니다.

  • PrivateRoute.js : /login사용자가 인증없이 보호 된 페이지에 액세스하려고하는 경우 리디렉션되는 메타 구성 요소입니다 .

  • ServerError.js : 다른 구성 요소는이 API를 사용하여 구성 요소가 처리 할 수없는 500 개의 오류로 API가 응답하는 경우 500 서버 오류 페이지를 표시합니다.

 

src/constants

src/constants/index.js

export const API_BASE_URL = process.env.REACT_APP_API_BASE_URL || 'http://localhost:8080/api';
export const ACCESS_TOKEN = 'accessToken';

export const POLL_LIST_SIZE = 30;
export const MAX_CHOICES = 6;
export const POLL_QUESTION_MAX_LENGTH = 140;
export const POLL_CHOICE_MAX_LENGTH = 40;

export const NAME_MIN_LENGTH = 4;
export const NAME_MAX_LENGTH = 40;

export const USERNAME_MIN_LENGTH = 3;
export const USERNAME_MAX_LENGTH = 15;

export const EMAIL_MAX_LENGTH = 40;

export const PASSWORD_MIN_LENGTH = 6;
export const PASSWORD_MAX_LENGTH = 20;

 

 

src / poll

  • NewPoll.js : 설문 조사 작성 양식을 표시

  • PollList.js :이 구성 요소는 투표 목록과 설문조사를 보여주는 데 사용되고, 사용자가 작성한 설문 목록과 해당 사용자가 투표 한 설문 목록을 렌더링합니다.

  • Poll.js : PollList구성 요소가 단일 폴을 렌더링하는 데 사용됩니다 .

 

src / user

  • login / Login.js : Login 구성 요소는 로그인 양식을 렌더링하여 사용자를 인증하는 로그인 API를 호출합니다.

  • signup / Signup.js : 등록 양식을 렌더링하고 클라이언트 측 유효성 검증을 포함합니다. React에서 폼 유효성 검사를 수행하는 방법을 배우고 싶은지 확인하는 것은 흥미로운 구성 요소입니다.

  • profile / Profile.js : 프로필 페이지는 사용자의 공개 프로필을 렌더링합니다. 사용자의 기본 정보, 사용자가 작성한 여론 조사 목록 및 사용자가 투표 한 여론 조사 목록을 표시합니다.

src / util

  • APIUtils.js :이 스크립트에는 모든 Rest API 호출이 기록됩니다. fetchAPI를 사용하여 백엔드 서버에 요청합니다.

  • Colors.js :이 유틸리티는 사용자의 아바타에서 사용할 임의의 색상을 얻는 데 사용됩니다.

  • Helpers.js : 날짜 형식을 지정하는 헬퍼 함수가 포함되어 있습니다.

 

 

앱 실행

 

cd polling-app-client

 

npm install && npm start