Merge branch 'fix_loading_cookies' of Asara/sudoscientist-js-frontend into master
This commit is contained in:
commit
d4651a2315
13 changed files with 3256 additions and 2582 deletions
4
TODO.md
4
TODO.md
|
@ -1,5 +1,5 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
1. Add filtering posts by tags
|
1. Fix up UX, return errors to screen instead of doing nothing.
|
||||||
2. Auth
|
2. Add filtering posts by tags
|
||||||
3. Comments
|
3. Comments
|
||||||
|
|
5622
package-lock.json
generated
5622
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -6,15 +6,15 @@
|
||||||
"axios": "^0.18.1",
|
"axios": "^0.18.1",
|
||||||
"github-markdown-css": "^3.0.1",
|
"github-markdown-css": "^3.0.1",
|
||||||
"react": "^16.10.2",
|
"react": "^16.10.2",
|
||||||
"react-cookie": "^4.0.1",
|
|
||||||
"react-dom": "^16.10.2",
|
"react-dom": "^16.10.2",
|
||||||
"react-markdown": "^4.2.2",
|
"react-markdown": "^4.2.2",
|
||||||
|
"react-mde": "^7.6.2",
|
||||||
"react-redux": "^7.1.1",
|
"react-redux": "^7.1.1",
|
||||||
"react-router-dom": "^5.1.2",
|
"react-router-dom": "^5.1.2",
|
||||||
"react-scripts": "3.0.0",
|
"react-scripts": "3.0.0",
|
||||||
"redux": "^4.0.4",
|
"redux": "^4.0.4",
|
||||||
"redux-thunk": "^2.3.0",
|
"redux-thunk": "^2.3.0",
|
||||||
"semantic-ui-react": "^0.87.3"
|
"universal-cookie": "^4.0.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#EEEEEE" />
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css">
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
|
|
2
src/actions/history.js
Normal file
2
src/actions/history.js
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
import { createBrowserHistory } from 'history'
|
||||||
|
export default createBrowserHistory()
|
|
@ -1,4 +1,5 @@
|
||||||
import sudoscientist from '../apis/sudoscientist';
|
import sudoscientist from '../apis/sudoscientist';
|
||||||
|
import history from './history'
|
||||||
|
|
||||||
export const fetchPosts = () => async (dispatch) => {
|
export const fetchPosts = () => async (dispatch) => {
|
||||||
const response = await sudoscientist.get('/blog');
|
const response = await sudoscientist.get('/blog');
|
||||||
|
@ -25,9 +26,21 @@ export const userLogin = (username, password) => async (dispatch) => {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
if (response.status === 401) {
|
if (response.status === 401) {
|
||||||
console.log("Please check your credentials")
|
dispatch({ type: 'USER_LOGIN', payload: null })
|
||||||
}
|
}
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
dispatch({ type: 'USER_LOGIN', payload: response })
|
dispatch({ type: 'USER_LOGIN', payload: response })
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const loadCookieToState = (data) => async (dispatch) => {
|
||||||
|
dispatch({ type: 'LOAD_COOKIE', data })
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export const newBlogPost = (payload) => async (dispatch) => {
|
||||||
|
const response = await sudoscientist.post('/blog', payload)
|
||||||
|
if (response.status === 201) {
|
||||||
|
history.push('/posts/' + response.data.slug)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
export default axios.create({
|
export default axios.create({
|
||||||
baseURL: 'http://api.sudosci.test:8080/v1/api/',
|
baseURL: 'https://api.sudoscientist.com/v1/api/',
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,12 +4,9 @@ import ReactMarkdown from 'react-markdown';
|
||||||
import AboutMarkdown from '../static/about.md';
|
import AboutMarkdown from '../static/about.md';
|
||||||
|
|
||||||
class About extends React.Component {
|
class About extends React.Component {
|
||||||
constructor () {
|
state = { about: '' };
|
||||||
super();
|
|
||||||
this.state = { about: '' };
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount() {
|
componentDidMount() {
|
||||||
fetch(AboutMarkdown).then(res => res.text()).then(text => this.setState({ markdown: text }));
|
fetch(AboutMarkdown).then(res => res.text()).then(text => this.setState({ markdown: text }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,25 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
|
import { Router, Route, Switch } from "react-router-dom";
|
||||||
import NavBar from './NavBar';
|
import NavBar from './NavBar';
|
||||||
import Footer from './Footer';
|
import Footer from './Footer';
|
||||||
import PostList from './PostList';
|
import PostList from './PostList';
|
||||||
import Post from './Post';
|
import Post from './Post';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
import About from './About'
|
import About from './About'
|
||||||
|
import NewPost from './NewPost'
|
||||||
|
import history from '../actions/history';
|
||||||
|
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Router>
|
<Router history={history}>
|
||||||
<NavBar/>
|
<NavBar/>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact path="/" component={PostList}/>
|
<Route exact path="/" component={PostList}/>
|
||||||
<Route exact path="/about/" component={About}/>
|
<Route exact path="/about/" component={About}/>
|
||||||
<Route exact path="/posts/" component={PostList}/>
|
<Route exact path="/posts/" component={PostList}/>
|
||||||
|
<Route exact path="/newpost/" component={NewPost}/>
|
||||||
<Route path="/posts/:slug" component={Post}/>
|
<Route path="/posts/:slug" component={Post}/>
|
||||||
<Route path="/users/:user" component={User}/>
|
<Route path="/users/:user" component={User}/>
|
||||||
</Switch>
|
</Switch>
|
||||||
|
|
|
@ -2,19 +2,47 @@ import React, { Component } from 'react';
|
||||||
import Cookies from 'universal-cookie';
|
import Cookies from 'universal-cookie';
|
||||||
|
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { userLogin } from '../actions';
|
import { userLogin, loadCookieToState } from '../actions';
|
||||||
|
|
||||||
class AuthMenu extends Component {
|
class AuthMenu extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
const cookies = new Cookies();
|
||||||
|
const datacookie = cookies.get('DataCookie');
|
||||||
|
|
||||||
|
if (datacookie) {
|
||||||
|
const data = JSON.parse(atob(datacookie.split('.')[1]))
|
||||||
|
if (data.exp > Math.floor(Date.now() / 1000)) {
|
||||||
|
this.state = {
|
||||||
|
user_authed: true,
|
||||||
|
where_in_auth_menu: "loggedIn",
|
||||||
|
username: data.username,
|
||||||
|
exp: data.exp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
this.state = {
|
this.state = {
|
||||||
user_authed: false,
|
user_authed: false,
|
||||||
where_in_auth_menu: "requestUsername",
|
where_in_auth_menu: "requestUsername",
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
email: ""
|
email: "",
|
||||||
|
exp: null,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.state = {
|
||||||
|
user_authed: false,
|
||||||
|
where_in_auth_menu: "requestUsername",
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
email: "",
|
||||||
|
exp: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.props.loadCookieToState(this.state)
|
||||||
|
|
||||||
this.handleInputChange = this.handleInputChange.bind(this)
|
this.handleInputChange = this.handleInputChange.bind(this)
|
||||||
|
|
||||||
|
@ -27,7 +55,6 @@ class AuthMenu extends Component {
|
||||||
this.handleCreateAccount = this.handleCreateAccount.bind(this)
|
this.handleCreateAccount = this.handleCreateAccount.bind(this)
|
||||||
|
|
||||||
this.authMenu = this.authMenu.bind(this)
|
this.authMenu = this.authMenu.bind(this)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -63,16 +90,15 @@ class AuthMenu extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
handleLogin() {
|
handleLogin() {
|
||||||
const cookies = new Cookies();
|
|
||||||
this.props.userLogin(this.state.username, this.state.password)
|
this.props.userLogin(this.state.username, this.state.password)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.setState(state => ({
|
this.setState(state => ({
|
||||||
auth_menu_visible: false
|
auth_menu_visible: false,
|
||||||
|
where_in_auth_menu: "loggedIn"
|
||||||
}))
|
}))
|
||||||
this.props.close()
|
this.props.close()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(console.log(cookies.getAll()))
|
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
})
|
})
|
||||||
|
@ -159,6 +185,15 @@ class AuthMenu extends Component {
|
||||||
<button onClick={this.handleCreateAccount} className="fluid ui positive button">Create Account!</button>
|
<button onClick={this.handleCreateAccount} className="fluid ui positive button">Create Account!</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
case 'loggedIn':
|
||||||
|
return (
|
||||||
|
<div className="ui menu dropdown" style={{display: "inline"}}>
|
||||||
|
<div className="ui left icon input">
|
||||||
|
<i className="edit icon"></i>
|
||||||
|
<button onClick={() => { document.location.href = "/newpost/"; }} className="fluid ui positive button">Create Post!</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -171,12 +206,7 @@ class AuthMenu extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
|
||||||
return {
|
|
||||||
username: state.username,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export default connect(
|
export default connect(
|
||||||
mapStateToProps,
|
null,
|
||||||
{ userLogin }
|
{ userLogin, loadCookieToState }
|
||||||
)(AuthMenu);
|
)(AuthMenu);
|
||||||
|
|
|
@ -7,8 +7,9 @@ class NavBar extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
this.state = {
|
this.state = {
|
||||||
logged_in: false,
|
user_authed: false,
|
||||||
auth_menu_visible: false,
|
auth_menu_visible: false,
|
||||||
|
user_or_login: 'Login'
|
||||||
}
|
}
|
||||||
this.handleLoginDropdown = this.handleLoginDropdown.bind(this)
|
this.handleLoginDropdown = this.handleLoginDropdown.bind(this)
|
||||||
}
|
}
|
||||||
|
@ -20,6 +21,7 @@ class NavBar extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { user } = this.props;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="ui four item secondary menu">
|
<div className="ui four item secondary menu">
|
||||||
|
@ -27,7 +29,7 @@ class NavBar extends Component {
|
||||||
<NavLink to="/posts/" className='item' activeClassName='active'>Posts</NavLink>
|
<NavLink to="/posts/" className='item' activeClassName='active'>Posts</NavLink>
|
||||||
<NavLink to="/about/" className='item' activeClassName='active'>About</NavLink>
|
<NavLink to="/about/" className='item' activeClassName='active'>About</NavLink>
|
||||||
<div onClick={this.handleLoginDropdown} className='item ui button dropdown'>
|
<div onClick={this.handleLoginDropdown} className='item ui button dropdown'>
|
||||||
Login
|
{user.user_authed ? user.username : 'Login'}
|
||||||
<i className="dropdown icon"></i>
|
<i className="dropdown icon"></i>
|
||||||
<AuthMenu auth_menu_visible={this.state.auth_menu_visible} close={ () => this.setState({auth_menu_visible: false})}></AuthMenu>
|
<AuthMenu auth_menu_visible={this.state.auth_menu_visible} close={ () => this.setState({auth_menu_visible: false})}></AuthMenu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -39,7 +41,7 @@ class NavBar extends Component {
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
const mapStateToProps = (state) => {
|
||||||
return {
|
return {
|
||||||
auth: state.auth
|
user: state.auth,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
67
src/components/NewPost.js
Normal file
67
src/components/NewPost.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import ReactMde from "react-mde";
|
||||||
|
import ReactMarkdown from 'react-markdown';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import 'github-markdown-css'
|
||||||
|
import "react-mde/lib/styles/css/react-mde-all.css"
|
||||||
|
import { newBlogPost } from '../actions';
|
||||||
|
|
||||||
|
const NewPost = (props) => {
|
||||||
|
const [title, setTitle] = React.useState("");
|
||||||
|
const [content, setContent] = React.useState("");
|
||||||
|
const [tags, setTags] = React.useState("");
|
||||||
|
const [selectedTab, setSelectedTab] = React.useState("write");
|
||||||
|
const username = useSelector(state => state.auth.username);
|
||||||
|
|
||||||
|
const submitPost = () => {
|
||||||
|
const payload = {
|
||||||
|
title: title,
|
||||||
|
content: content,
|
||||||
|
tags: tags,
|
||||||
|
author: username
|
||||||
|
}
|
||||||
|
props.newBlogPost(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<div className="container">
|
||||||
|
<input
|
||||||
|
value={title}
|
||||||
|
placeholder="Title..."
|
||||||
|
onChange={e => setTitle(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="markdown-body">
|
||||||
|
<ReactMde
|
||||||
|
value={content}
|
||||||
|
onChange={setContent}
|
||||||
|
selectedTab={selectedTab}
|
||||||
|
onTabChange={setSelectedTab}
|
||||||
|
generateMarkdownPreview={(markdown) =>
|
||||||
|
Promise.resolve(<ReactMarkdown source={markdown} />)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<input
|
||||||
|
value={tags}
|
||||||
|
placeholder="Comma,Seperated,Tags..."
|
||||||
|
onChange={e => setTags(e.target.value)}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<button onClick={submitPost}>Submit Post</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
user: state.auth,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
mapStateToProps,
|
||||||
|
{ newBlogPost }
|
||||||
|
)(NewPost);
|
||||||
|
|
|
@ -1,18 +1,24 @@
|
||||||
const initialState = {
|
const initialState = {
|
||||||
user_logged_in: false,
|
user_authed: false,
|
||||||
username: '',
|
where_in_auth_menu: "requestUsername",
|
||||||
|
email: "",
|
||||||
|
exp: null
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export default (state = initialState, action) => {
|
export default (state = initialState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
|
case 'LOAD_COOKIE':
|
||||||
|
return {...state, ...action.data}
|
||||||
case 'USER_LOGIN':
|
case 'USER_LOGIN':
|
||||||
|
if (action.payload) {
|
||||||
|
var data = JSON.parse(atob(action.payload.data.split('.')[1]))
|
||||||
return {...state, ...{
|
return {...state, ...{
|
||||||
username: action.payload.data.username,
|
user_authed: true,
|
||||||
user_logged_in: true
|
username: data.username,
|
||||||
}}
|
}}
|
||||||
|
}
|
||||||
|
else return;
|
||||||
default:
|
default:
|
||||||
return state;
|
return state
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
Reference in a new issue