Skip to content

Commit

Permalink
[react] App loading 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
myungwoo committed Oct 18, 2017
1 parent 293fdf1 commit ca3ffbf
Show file tree
Hide file tree
Showing 8 changed files with 251 additions and 169 deletions.
156 changes: 35 additions & 121 deletions client/src/App.js
Original file line number Diff line number Diff line change
@@ -1,148 +1,62 @@
import React from 'react';
import {Route, Switch, Redirect} from 'react-router-dom';
import {AppBar, Toolbar, Typography, Button, IconButton, Drawer, Divider, Snackbar, Tooltip} from 'material-ui';
import MenuIcon from 'material-ui-icons/Menu';
import WebIcon from 'material-ui-icons/Web';
import AccountCircleIcon from 'material-ui-icons/AccountCircle';
import DescriptionIcon from 'material-ui-icons/Description';
import LibraryBooksIcon from 'material-ui-icons/LibraryBooks';
import HighlightOffIcon from 'material-ui-icons/HighlightOff';
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List';
import {Snackbar} from 'material-ui';

import Auth from './auth';
import Login from './auth/login';
import Auth from './Auth';

import {ClockIcon} from './components/icons';
import Timer from './components/timer';
import UserInfoDialog from './components/user-info-dialog';
import Main from './Main';
import Login from './Login';

import './App.css';

class A extends React.Component {
render() {
return (<h1>A</h1>);
}
}
import Loading from './components/loading';

class B extends React.Component {
componentDidMount() {
const {toast} = this.props;
toast('B');
}

render() {
return (<h1>B</h1>);
}
}
import './App.css';

class App extends React.Component {
constructor(props) {
super(props);
this.state = {
user: Auth.getUser(),
open: false,
error: false,
loading: true,
user: null,

timer_open: true,
dialog_open: false,
snackbar_open: false,
snackbar_message: ''
};
}

toast(message) {
this.setState({snackbar_open: true, snackbar_message: message});
componentDidMount() {
if (!Auth.getUser()){
this.setState({loading: false});
return;
}
Auth.validateUser()
.then(res => {
this.setState({user: res, loading: false});
})
.catch(() => {
this.setState({error: true});
});
}

logout() {
Auth.doLogout(); this.setState({user: Auth.getUser()});
toast(message) {
this.setState({snackbar_open: true, snackbar_message: message});
}

render() {
return (
<div>
{this.state.user ?
(<div>
<AppBar>
<Toolbar>
<IconButton
color="contrast"
aria-label="open drawer"
onClick={() => this.setState({open: true})}
>
<MenuIcon />
</IconButton>
<Typography type="title" color="inherit" style={{flex: 1}}>
DOMJudge
</Typography>
<Tooltip placement="bottom" title="Contest starts in">
<Button color="contrast" onClick={() => this.setState({timer_open: !this.state.timer_open})}>
<ClockIcon />
{this.state.timer_open &&
<Timer
style={{paddingLeft: 10}}
/>}
</Button>
</Tooltip>
<Button color="contrast" onClick={() => this.setState({dialog_open: true})}>
<AccountCircleIcon />
</Button>
</Toolbar>
</AppBar>
<UserInfoDialog
user={this.state.user}
open={this.state.dialog_open}
onRequestClose={() => this.setState({dialog_open: false})} />
<Drawer open={this.state.open} onRequestClose={() => this.setState({open: false})}>
<div>
<List style={{width: 250}}>
<ListItem button onClick={() => {this.setState({open: false}); window.location = '#/';}}>
<ListItemIcon>
<DescriptionIcon />
</ListItemIcon>
<ListItemText primary="Overview" />
</ListItem>
<ListItem button onClick={() => {this.setState({open: false}); window.location = '#/B';}}>
<ListItemIcon>
<LibraryBooksIcon />
</ListItemIcon>
<ListItemText primary="Problems" />
</ListItem>
<ListItem button>
<ListItemIcon>
<WebIcon />
</ListItemIcon>
<ListItemText primary="Scoreboard" />
</ListItem>
</List>
<Divider />
<List>
<ListItem button>
<ListItemIcon>
<HighlightOffIcon />
</ListItemIcon>
<ListItemText primary="Logout" onClick={() => {this.setState({open: false}); this.logout();}} />
</ListItem>
</List>
</div>
</Drawer>
<div style={{
width: '100%', padding: '86px 15px 15px 20px'
}}>
<div style={{
background: '#fafafa',
border: '1px solid #ebebeb',
boxShadow: 'rgba(0, 0, 0, 0.14902) 0px 1px 1px 0px, rgba(0, 0, 0, 0.09804) 0px 1px 2px 0px'
}}>
<Switch>
<Route exact path="/" render={props => (<A {...props} toast={this.toast.bind(this)} />)} />
<Route exact path="/B" render={props => (<B {...props} toast={this.toast.bind(this)} />)} />
<Redirect to="/" />
</Switch>
</div>
</div>
</div>)
{this.state.error ?
<div style={{textAlign: 'center', paddingTop: 100, fontWeight: 900, fontSize: 40, color: 'red', lineHeight: 2}}>
Cannot connect to API server.<br /> Please contact administrator.
</div>
: this.state.loading ?
<div style={{textAlign: 'center', paddingTop: 100, fontWeight: 900, fontSize: 40}}>
Application is loading, please wait...
<Loading />
</div>
: this.state.user ?
<Main toast={this.toast.bind(this)} onLogout={() => this.setState({user: Auth.getUser()})} user={this.state.user} />
:
<Login toast={this.toast.bind(this)} afterLogin={() => this.setState({user: Auth.getUser()})} />
<Login toast={this.toast.bind(this)} onLogin={() => this.setState({user: Auth.getUser()})} />
}
<Snackbar
anchorOrigin={{vertical: 'top', horizontal: 'right'}}
Expand Down
53 changes: 53 additions & 0 deletions client/src/Auth.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import axios from 'axios';

const Auth = {
doLogin: async function(username, password) {
try{
let res = await axios.post('/api/auth/login', {
username: username,
password: password
});
if (res.data.success){
localStorage.setItem('jwt-token', res.data.token);
localStorage.setItem('userdata', JSON.stringify(res.data.userdata));
return null;
}else{
return res.data.error;
}
} catch (err) {
return 'connection';
}
},

doLogout: () => {
localStorage.removeItem('jwt-token');
localStorage.removeItem('userdata');
},

validateUser: async function() {
let res = await axios.get('/api/auth/user', Auth.getHeader());
if (res.data){
localStorage.setItem('userdata', JSON.stringify(res.data));
}else{
localStorage.removeItem('jwt-token');
localStorage.removeItem('userdata');
}
return res;
},

getUser: () => {
try{
if (localStorage.getItem('jwt-token'))
return JSON.parse(localStorage.getItem('userdata') || 'null');
return null;
}catch(err){
localStorage.removeItem('jwt-token');
localStorage.removeItem('userdata');
return null;
}
},

getHeader: () => ({headers: {'x-access-token': localStorage.getItem('jwt-token')}})
};

export default Auth;
15 changes: 11 additions & 4 deletions client/src/auth/login.js → client/src/Login.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react';
import Auth from '../auth';
import PropTypes from 'prop-types';

import Auth from './Auth';
import {TextField, Button} from 'material-ui';

import Loading from '../components/loading';
import Loading from './components/loading';

class LoginPage extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -31,15 +33,15 @@ class LoginPage extends React.Component {
}
else{
this.toast('Welcome!');
this.props.afterLogin();
this.props.onLogin();
}
});
}

render() {
return (
<div>
<Loading loading={this.state.loading} />
{this.state.loading && <Loading loading={this.state.loading} />}
<hgroup style={{textAlign: 'center', marginTop: '4em'}}>
<h1 style={{fontWeight: 300, color: '#636363'}}><a href="http://domjudge.org" target="_blank" rel="noopener noreferrer">DOMJudge</a> for React</h1>
<h3 style={{fontWeight: 300, color: '#4a89dc'}}>Powered by <a href="https://github.com/myungwoo" target="_blank" rel="noopener noreferrer">Myungwoo Chun</a></h3>
Expand Down Expand Up @@ -76,4 +78,9 @@ class LoginPage extends React.Component {
}
}

LoginPage.PropTypes = {
toast: PropTypes.object.isRequired,
onLogin: PropTypes.object.isRequired
}

export default LoginPage;
Loading

0 comments on commit ca3ffbf

Please sign in to comment.