Refactor
This commit is contained in:
@@ -9,6 +9,10 @@ export const TOGGLE_EDITORS = 'TOGGLE_EDITORS';
|
||||
export const ADD_REVISION = 'ADD_REVISION';
|
||||
export const REPLACE_TEXT= 'REPLACE_TEXT';
|
||||
|
||||
export const SLACK_AUTH_PENDING = 'SLACK_AUTH_PENDING';
|
||||
export const SLACK_AUTH_SUCCESS = 'SLACK_AUTH_SUCCESS';
|
||||
export const SLACK_AUTH_FAILURE = 'SLACK_AUTH_FAILURE';
|
||||
|
||||
/*
|
||||
* Other Constants
|
||||
*/
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import MarkdownIcon from '../../Components/Icons/Markdown';
|
||||
|
||||
import styles from './Header.scss';
|
||||
import classNames from 'classnames/bind';
|
||||
const cx = classNames.bind(styles);
|
||||
|
||||
const Header = ({
|
||||
activeEditors,
|
||||
toggleEditors,
|
||||
}) => {
|
||||
return (
|
||||
@@ -17,16 +14,7 @@ const Header = ({
|
||||
className={ cx('headerItem', 'editorToggle') }
|
||||
style={{ display: 'flex', justifyContent: 'flex-end', alignItems: 'center' }}
|
||||
>
|
||||
<div
|
||||
onClick={toggleEditors.bind(this, 'MARKDOWN')}
|
||||
className={ activeEditors.includes('MARKDOWN') ? styles.active : '' }
|
||||
>
|
||||
<MarkdownIcon style={{ width: '32px', height: '20px', color: '#fff' }} />
|
||||
</div>
|
||||
<div
|
||||
onClick={toggleEditors.bind(this, 'TEXT')}
|
||||
className={ activeEditors.includes('TEXT') ? styles.active : '' }
|
||||
>
|
||||
<div>
|
||||
<span className={ styles.textIcon }>Aa</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
width: 150px;
|
||||
padding: 12px 22px;
|
||||
|
||||
font-size: 13px;
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
font-family: "Atlas Grotesk", "Helvetica Neue", sans-serif;
|
||||
text-align: center;
|
||||
|
||||
@@ -3,12 +3,13 @@ import Codemirror from 'react-codemirror';
|
||||
import 'codemirror/mode/gfm/gfm';
|
||||
import 'codemirror/mode/javascript/javascript';
|
||||
import 'codemirror/addon/edit/continuelist';
|
||||
import 'codemirror/addon/display/placeholder.js';
|
||||
import Dropzone from 'react-dropzone';
|
||||
|
||||
import styles from './MarkdownEditor.scss';
|
||||
import './codemirror.css';
|
||||
import './codemirror.scss';
|
||||
|
||||
import { client } from '../../Utils/ApiClient';
|
||||
import { client } from '../../utils/ApiClient';
|
||||
|
||||
class MarkdownAtlas extends React.Component {
|
||||
static propTypes = {
|
||||
@@ -97,6 +98,7 @@ class MarkdownAtlas extends React.Component {
|
||||
extraKeys: {
|
||||
Enter: 'newlineAndIndentContinueMarkdownList',
|
||||
},
|
||||
placeholder: "# Start with a title...",
|
||||
};
|
||||
|
||||
// http://codepen.io/lubelski/pen/fnGae
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
|
||||
Name: Base16 Default Light
|
||||
Author: Chris Kempson (http://chriskempson.com)
|
||||
|
||||
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
|
||||
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
|
||||
|
||||
*/
|
||||
|
||||
@import url(https://fonts.googleapis.com/css?family=Cousine:400,700,700italic,400italic);
|
||||
|
||||
/* Custom styling */
|
||||
.cm-s-atlas.CodeMirror {
|
||||
background: #ffffff;
|
||||
color: #202020;
|
||||
font-family: 'Atlas Typewriter', 'Cousine', 'Monaco', monospace;
|
||||
font-weight: 300;
|
||||
height: auto;
|
||||
}
|
||||
.cm-s-atlas div.CodeMirror-selected {
|
||||
background: #90CAF9;
|
||||
}
|
||||
|
||||
/* Disable ondrag cursor for file uploads */
|
||||
.cm-s-atlas div.CodeMirror-dragcursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.cm-s-atlas .CodeMirror-line::selection,
|
||||
.cm-s-atlas .CodeMirror-line > span::selection,
|
||||
.cm-s-atlas .CodeMirror-line > span > span::selection {
|
||||
background: #90CAF9;
|
||||
}
|
||||
|
||||
.cm-s-atlas .CodeMirror-line::-moz-selection, .cm-s-atlas .CodeMirror-line > span::-moz-selection, .cm-s-atlas .CodeMirror-line > span > span::-moz-selection { background: #e0e0e0; }
|
||||
.cm-s-atlas .CodeMirror-gutters { background: #f5f5f5; border-right: 0px; }
|
||||
.cm-s-atlas .CodeMirror-guttermarker { color: #ac4142; }
|
||||
.cm-s-atlas .CodeMirror-guttermarker-subtle { color: #b0b0b0; }
|
||||
.cm-s-atlas .CodeMirror-linenumber { color: #b0b0b0; }
|
||||
.cm-s-atlas .CodeMirror-cursor {
|
||||
border-left: 2px solid #2196F3;
|
||||
}
|
||||
|
||||
.cm-s-atlas span.cm-quote {
|
||||
font-style: italic;
|
||||
}
|
||||
.cm-s-atlas span.cm-comment { color: #8f5536; }
|
||||
.cm-s-atlas span.cm-atom { color: #aa759f; }
|
||||
.cm-s-atlas span.cm-number { color: #aa759f; }
|
||||
|
||||
.cm-s-atlas span.cm-property, .cm-s-atlas span.cm-attribute { color: #90a959; }
|
||||
.cm-s-atlas span.cm-keyword { color: #ac4142; }
|
||||
.cm-s-atlas span.cm-string { color: #f4bf75; }
|
||||
|
||||
.cm-s-atlas span.cm-variable { color: #90a959; }
|
||||
.cm-s-atlas span.cm-variable-2 { color: #788696; }
|
||||
.cm-s-atlas span.cm-def { color: #d28445; }
|
||||
.cm-s-atlas span.cm-bracket { color: #202020; }
|
||||
.cm-s-atlas span.cm-tag { color: #ac4142; }
|
||||
.cm-s-atlas span.cm-link { color: #aa759f; }
|
||||
.cm-s-atlas span.cm-error { background: #ac4142; color: #505050; }
|
||||
|
||||
.cm-s-atlas .CodeMirror-activeline-background { background: #DDDCDC; }
|
||||
.cm-s-atlas .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }
|
||||
@@ -11,9 +11,7 @@ const keys = keyMirror({
|
||||
// Constant values
|
||||
const constants = {
|
||||
API_USER_AGENT: `${name}/${version}`,
|
||||
API_BASE_URL: 'http://localhost:8000/api',
|
||||
LOGIN_PATH: '/login',
|
||||
LOGIN_SUCCESS_PATH: '/dashboard',
|
||||
API_BASE_URL: 'http://localhost:3000/api',
|
||||
};
|
||||
|
||||
export default Object.assign(keys, constants);
|
||||
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
TOGGLE_HISTORY_SIDEBAR,
|
||||
ADD_REVISION,
|
||||
REPLACE_TEXT,
|
||||
ActiveEditors,
|
||||
} from '../Actions';
|
||||
ActiveEditors
|
||||
} from '../actions';
|
||||
|
||||
const activeEditors = (state = [ActiveEditors.MARKDOWN, ActiveEditors.TEXT], action) => {
|
||||
switch (action.type) {
|
||||
@@ -103,8 +103,13 @@ const text = (state = textDefaultState, action) => {
|
||||
}
|
||||
};
|
||||
|
||||
import team from './team';
|
||||
import user from './user';
|
||||
|
||||
export default combineReducers({
|
||||
activeEditors,
|
||||
historySidebar,
|
||||
text,
|
||||
user,
|
||||
team,
|
||||
});
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
import Auth from './Auth';
|
||||
import Constants from '../Constants';
|
||||
import auth from './auth';
|
||||
import constants from '../constants';
|
||||
|
||||
class ApiClient {
|
||||
constructor(options = {}) {
|
||||
this.baseUrl = options.baseUrl || Constants.API_BASE_URL;
|
||||
this.userAgent = options.userAgent || Constants.API_USER_AGENT;
|
||||
this.baseUrl = options.baseUrl || constants.API_BASE_URL;
|
||||
this.userAgent = options.userAgent || constants.API_USER_AGENT;
|
||||
}
|
||||
|
||||
fetch = (path, method, data) => {
|
||||
@@ -25,8 +25,8 @@ class ApiClient {
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': this.userAgent,
|
||||
});
|
||||
if (Auth.getToken()) {
|
||||
headers.set('Authorization', `JWT ${Auth.getToken()}`);
|
||||
if (auth.getToken()) {
|
||||
headers.set('Authorization', `Bearer ${auth.getToken()}`);
|
||||
}
|
||||
|
||||
// Construct request
|
||||
@@ -48,7 +48,7 @@ class ApiClient {
|
||||
|
||||
// Handle 401, log out user
|
||||
if (response.status === 401) {
|
||||
Auth.logout();
|
||||
auth.logout(); // replace with dispatch+action
|
||||
}
|
||||
|
||||
// Handle failed responses
|
||||
@@ -81,18 +81,6 @@ class ApiClient {
|
||||
return this.fetch(path, 'POST', data);
|
||||
}
|
||||
|
||||
put = (path, data) => {
|
||||
return this.fetch(path, 'PUT', data);
|
||||
}
|
||||
|
||||
get = (path, data) => {
|
||||
return this.fetch(path, 'GET', data);
|
||||
}
|
||||
|
||||
delete = (path, data) => {
|
||||
return this.fetch(path, 'DELETE', data);
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
constructQueryString = (data) => {
|
||||
|
||||
@@ -1,42 +1,19 @@
|
||||
// Inspired by https://github.com/reactjs/react-router/blob/master/examples/auth-flow/auth.js
|
||||
import Constants from '../Constants';
|
||||
import History from './History';
|
||||
|
||||
import { client } from './ApiClient';
|
||||
import constants from '../constants';
|
||||
|
||||
export default {
|
||||
login(email, password) {
|
||||
return new Promise((resolve, reject) => {
|
||||
client.post('/authenticate', {
|
||||
email,
|
||||
password,
|
||||
})
|
||||
.then((data) => {
|
||||
localStorage.setItem(Constants.JWT_STORE_KEY, data.jwt_token);
|
||||
this.onChange(true);
|
||||
resolve(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
setToken(token) {
|
||||
localStorage.setItem(constants.JWT_STORE_KEY, token);
|
||||
},
|
||||
|
||||
getToken() {
|
||||
return localStorage.getItem(Constants.JWT_STORE_KEY);
|
||||
return localStorage.getItem(constants.JWT_STORE_KEY);
|
||||
},
|
||||
|
||||
logout() {
|
||||
localStorage.removeItem(Constants.JWT_STORE_KEY);
|
||||
History.push(Constants.LOGIN_PATH);
|
||||
this.onChange(false);
|
||||
localStorage.removeItem(constants.JWT_STORE_KEY);
|
||||
},
|
||||
|
||||
loggedIn() {
|
||||
return !!localStorage.getItem(Constants.JWT_STORE_KEY);
|
||||
},
|
||||
|
||||
onChange() {
|
||||
// This is overriden with a callback function in `Views/App/App.js`
|
||||
return !!localStorage.getItem(constants.JWT_STORE_KEY);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import MarkdownEditor from '../../Components/MarkdownEditor';
|
||||
import TextEditor from '../../Components/TextEditor';
|
||||
|
||||
import { toMarkdown } from '../../Utils/Markdown';
|
||||
import {
|
||||
updateText,
|
||||
replaceText,
|
||||
} from '../../Actions';
|
||||
|
||||
import Constants from '../../Constants';
|
||||
|
||||
import styles from './Dashboard.scss';
|
||||
|
||||
class Dashboard extends Component {
|
||||
static propTypes = {
|
||||
editMarkdown: React.PropTypes.func.isRequired,
|
||||
editText: React.PropTypes.func.isRequired,
|
||||
text: React.PropTypes.string,
|
||||
replaceText: React.PropTypes.func.isRequired,
|
||||
activeEditors: React.PropTypes.arrayOf(React.PropTypes.string),
|
||||
showHistorySidebar: React.PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
const activeEditors = this.props.activeEditors;
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{
|
||||
activeEditors.includes('MARKDOWN') ? (
|
||||
<div className={ `${activeEditors.length > 1 ?
|
||||
styles.panel : styles.fullscreen} ${styles.markdown}`}
|
||||
>
|
||||
<MarkdownEditor
|
||||
onChange={this.props.editMarkdown}
|
||||
text={this.props.text}
|
||||
replaceText={this.props.replaceText}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
{
|
||||
activeEditors.includes('TEXT') ? (
|
||||
<div className={ `${activeEditors.length > 1 ?
|
||||
styles.panel : styles.fullscreen} ${styles.text}`}
|
||||
>
|
||||
<TextEditor onChange={this.props.editText} text={this.props.text} />
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
text: state.text.text,
|
||||
editor: state.editor,
|
||||
activeEditors: state.activeEditors,
|
||||
showHistorySidebar: state.historySidebar.visible,
|
||||
revisions: state.text.revisions,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
editMarkdown: (text) => {
|
||||
dispatch(updateText(text, 'markdown'));
|
||||
},
|
||||
editText: (html) => {
|
||||
const text = toMarkdown(html);
|
||||
dispatch(updateText(text, 'text'));
|
||||
},
|
||||
replaceText: (originalText, replacedText) => {
|
||||
dispatch(replaceText(originalText, replacedText));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Dashboard = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Dashboard);
|
||||
|
||||
export default Dashboard;
|
||||
@@ -1,75 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
|
||||
import Auth from '../../Utils/Auth';
|
||||
|
||||
export default class Login extends Component {
|
||||
static propTypes = {
|
||||
location: React.PropTypes.object,
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
router: React.PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
email: '',
|
||||
password: '',
|
||||
error: null,
|
||||
}
|
||||
|
||||
handleEmailChange = (event) => {
|
||||
this.setState({ email: event.target.value });
|
||||
}
|
||||
|
||||
handlePasswordChange = (event) => {
|
||||
this.setState({ password: event.target.value });
|
||||
}
|
||||
|
||||
handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
Auth.login(this.state.email, this.state.password)
|
||||
.then(() => {
|
||||
const { location } = this.props;
|
||||
|
||||
if (location.state && location.state.nextPathname) {
|
||||
this.context.router.replace(location.state.nextPathname);
|
||||
} else {
|
||||
this.context.router.replace('/dashboard');
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.setState({ error: err.error });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<h2>Login</h2>
|
||||
<form action="" onSubmit={ this.handleSubmit }>
|
||||
{this.state.error && (
|
||||
<p>{ this.state.error }</p>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<input
|
||||
placeholder={ 'Email' }
|
||||
onChange={ this.handleEmailChange }
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input
|
||||
placeholder={ 'Password' }
|
||||
type={ 'password' }
|
||||
onChange={ this.handlePasswordChange }
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<input type={ 'submit' } />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
import Login from './Login';
|
||||
export default Login;
|
||||
5
src/actions/EditorActions.js
Normal file
5
src/actions/EditorActions.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import makeActionCreator from '../utils/actions';
|
||||
|
||||
export const TOGGLE_PREVIEW = 'TOGGLE_PREVIEW';
|
||||
|
||||
const togglePreview = makeActionCreator(TOGGLE_PREVIEW);
|
||||
34
src/actions/SlackAuthAction.js
Normal file
34
src/actions/SlackAuthAction.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import makeActionCreator from '../utils/actions';
|
||||
import { push } from 'react-router-redux';
|
||||
import { client } from 'utils/ApiClient';
|
||||
import auth from 'utils/auth';
|
||||
|
||||
import { updateUser } from './UserActions';
|
||||
import { updateTeam } from './TeamActions';
|
||||
|
||||
export const SLACK_AUTH_PENDING = 'SLACK_AUTH_PENDING';
|
||||
export const SLACK_AUTH_SUCCESS = 'SLACK_AUTH_SUCCESS';
|
||||
export const SLACK_AUTH_FAILURE = 'SLACK_AUTH_FAILURE';
|
||||
|
||||
const slackAuthPending = makeActionCreator(SLACK_AUTH_PENDING);
|
||||
const slackAuthSuccess = makeActionCreator(SLACK_AUTH_SUCCESS, 'user');
|
||||
const slackAuthFailure = makeActionCreator(SLACK_AUTH_FAILURE, 'error');
|
||||
|
||||
export function slackAuthAsync(code) {
|
||||
return (dispatch) => {
|
||||
dispatch(slackAuthPending());
|
||||
|
||||
client.post('/auth.slack', {
|
||||
code: code,
|
||||
})
|
||||
.then(data => {
|
||||
auth.setToken(data.data.accessToken);
|
||||
dispatch(updateUser(data.data.user));
|
||||
dispatch(updateTeam(data.data.team));
|
||||
dispatch(push('/dashboard'));
|
||||
})
|
||||
// .catch((err) => {
|
||||
// dispatch(push('/error'));
|
||||
// })
|
||||
};
|
||||
};
|
||||
5
src/actions/TeamActions.js
Normal file
5
src/actions/TeamActions.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import makeActionCreator from '../utils/actions';
|
||||
|
||||
export const UPDATE_TEAM = 'UPDATE_TEAM';
|
||||
|
||||
export const updateTeam = makeActionCreator(UPDATE_TEAM, 'team');
|
||||
5
src/actions/UserActions.js
Normal file
5
src/actions/UserActions.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import makeActionCreator from '../utils/actions';
|
||||
|
||||
export const UPDATE_USER = 'UPDATE_USER';
|
||||
|
||||
export const updateUser = makeActionCreator(UPDATE_USER, 'user');
|
||||
0
src/components/Button/Button.scss
Normal file
0
src/components/Button/Button.scss
Normal file
2
src/components/Button/index.js
Normal file
2
src/components/Button/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import Button from './Button';
|
||||
export default Button;
|
||||
60
src/components/Editor/Editor.js
Normal file
60
src/components/Editor/Editor.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import MarkdownEditor from '../../components/MarkdownEditor';
|
||||
|
||||
import {
|
||||
updateText,
|
||||
replaceText,
|
||||
} from '../../actions';
|
||||
|
||||
import constants from '../../constants';
|
||||
|
||||
import styles from './Editor.scss';
|
||||
|
||||
class Editor extends Component {
|
||||
static propTypes = {
|
||||
editMarkdown: React.PropTypes.func.isRequired,
|
||||
text: React.PropTypes.string,
|
||||
replaceText: React.PropTypes.func.isRequired,
|
||||
showHistorySidebar: React.PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={ styles.markdown }>
|
||||
<MarkdownEditor
|
||||
onChange={this.props.editMarkdown}
|
||||
text={this.props.text}
|
||||
replaceText={this.props.replaceText}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
text: state.text.text,
|
||||
editor: state.editor,
|
||||
showHistorySidebar: state.historySidebar.visible,
|
||||
revisions: state.text.revisions,
|
||||
};
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
editMarkdown: (text) => {
|
||||
dispatch(updateText(text, 'markdown'));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
Editor = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps,
|
||||
)(Editor);
|
||||
|
||||
export default Editor;
|
||||
2
src/components/Editor/index.js
Normal file
2
src/components/Editor/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import Editor from './Editor';
|
||||
export default Editor;
|
||||
34
src/components/Layout/Layout.js
Normal file
34
src/components/Layout/Layout.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import styles from './Layout.scss';
|
||||
|
||||
class Layout extends React.Component {
|
||||
static propTypes = {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<div className={ styles.header }>
|
||||
<div className={ styles.teamName }>Coinbase</div>
|
||||
<div className={ styles.user }>
|
||||
<img src={ this.props.avatarUrl } />
|
||||
</div>
|
||||
</div>
|
||||
<div className={ styles.content }>
|
||||
{ this.props.children }
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
return {
|
||||
avatarUrl: state.user ? state.user.avatarUrl : null,
|
||||
}
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps)(Layout);
|
||||
35
src/components/Layout/Layout.scss
Normal file
35
src/components/Layout/Layout.scss
Normal file
@@ -0,0 +1,35 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
|
||||
height: 42px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.teamName {
|
||||
font-family: 'Atlas Grotesk';
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.user {
|
||||
|
||||
img {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
}
|
||||
2
src/components/Layout/index.js
Normal file
2
src/components/Layout/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import Layout from './Layout';
|
||||
export default Layout;
|
||||
66
src/components/MarkdownEditor/codemirror.scss
Normal file
66
src/components/MarkdownEditor/codemirror.scss
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
|
||||
Name: Base16 Default Light
|
||||
Author: Chris Kempson (http://chriskempson.com)
|
||||
|
||||
CodeMirror template by Jan T. Sott (https://github.com/idleberg/base16-codemirror)
|
||||
Original Base16 color scheme by Chris Kempson (https://github.com/chriskempson/base16)
|
||||
|
||||
*/
|
||||
|
||||
:global {
|
||||
/* Custom styling */
|
||||
.cm-s-atlas.CodeMirror {
|
||||
background: #ffffff;
|
||||
color: #202020;
|
||||
font-family: 'Atlas Typewriter', 'Cousine', 'Monaco', monospace;
|
||||
font-weight: 300;
|
||||
height: auto;
|
||||
}
|
||||
.cm-s-atlas div.CodeMirror-selected {
|
||||
background: #90CAF9;
|
||||
}
|
||||
|
||||
/* Disable ondrag cursor for file uploads */
|
||||
.cm-s-atlas div.CodeMirror-dragcursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.cm-s-atlas .CodeMirror-line::selection,
|
||||
.cm-s-atlas .CodeMirror-line > span::selection,
|
||||
.cm-s-atlas .CodeMirror-line > span > span::selection {
|
||||
background: #90CAF9;
|
||||
}
|
||||
|
||||
.cm-s-atlas .CodeMirror-line::-moz-selection, .cm-s-atlas .CodeMirror-line > span::-moz-selection, .cm-s-atlas .CodeMirror-line > span > span::-moz-selection { background: #e0e0e0; }
|
||||
.cm-s-atlas .CodeMirror-gutters { background: #f5f5f5; border-right: 0px; }
|
||||
.cm-s-atlas .CodeMirror-guttermarker { color: #ac4142; }
|
||||
.cm-s-atlas .CodeMirror-guttermarker-subtle { color: #b0b0b0; }
|
||||
.cm-s-atlas .CodeMirror-linenumber { color: #b0b0b0; }
|
||||
.cm-s-atlas .CodeMirror-cursor {
|
||||
border-left: 2px solid #2196F3;
|
||||
}
|
||||
|
||||
.cm-s-atlas span.cm-quote {
|
||||
font-style: italic;
|
||||
}
|
||||
.cm-s-atlas span.cm-comment { color: #8f5536; }
|
||||
.cm-s-atlas span.cm-atom { color: #aa759f; }
|
||||
.cm-s-atlas span.cm-number { color: #aa759f; }
|
||||
|
||||
.cm-s-atlas span.cm-property, .cm-s-atlas span.cm-attribute { color: #90a959; }
|
||||
.cm-s-atlas span.cm-keyword { color: #ac4142; }
|
||||
.cm-s-atlas span.cm-string { color: #f4bf75; }
|
||||
|
||||
.cm-s-atlas span.cm-variable { color: #90a959; }
|
||||
.cm-s-atlas span.cm-variable-2 { color: #788696; }
|
||||
.cm-s-atlas span.cm-def { color: #d28445; }
|
||||
.cm-s-atlas span.cm-bracket { color: #202020; }
|
||||
.cm-s-atlas span.cm-tag { color: #ac4142; }
|
||||
.cm-s-atlas span.cm-link { color: #aa759f; }
|
||||
.cm-s-atlas span.cm-error { background: #ac4142; color: #505050; }
|
||||
|
||||
.cm-s-atlas .CodeMirror-activeline-background { background: #DDDCDC; }
|
||||
.cm-s-atlas .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; }
|
||||
.cm-s-atlas .CodeMirror-placeholder { color: rgba(0, 0, 0, 0.5); font-weight: bold; }
|
||||
}
|
||||
0
src/components/Preview/Preview.js
Normal file
0
src/components/Preview/Preview.js
Normal file
2
src/components/Preview/index.js
Normal file
2
src/components/Preview/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import Preview from './Preview';
|
||||
export default Preview;
|
||||
43
src/components/SlackAuthLink/SlackAuthLink.js
Normal file
43
src/components/SlackAuthLink/SlackAuthLink.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
|
||||
import styles from './SlackAuthLink.scss';
|
||||
|
||||
export default class SlackAuthLink extends React.Component {
|
||||
static propTypes = {
|
||||
scopes: React.PropTypes.arrayOf(React.PropTypes.string),
|
||||
}
|
||||
|
||||
state = {
|
||||
oauthState: Math.random().toString(36).substring(7),
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
scopes: ['identify']
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
localStorage.oauthState = this.state.oauthState;
|
||||
}
|
||||
|
||||
slackUrl = () => {
|
||||
const baseUrl = 'https://slack.com/oauth/authorize';
|
||||
const params = {
|
||||
client_id: '30086650419.30130733398',
|
||||
scope: this.props.scopes.join(" "),
|
||||
redirect_uri: 'http://localhost:3000/auth/slack/',
|
||||
state: this.state.oauthState,
|
||||
};
|
||||
|
||||
const urlParams = Object.keys(params).map(function(key) {
|
||||
return key + '=' + encodeURIComponent(params[key]);
|
||||
}).join('&');
|
||||
|
||||
return `${baseUrl}?${urlParams}`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<a href={ this.slackUrl() } className={ styles.link }>Authorize /w Slack</a>
|
||||
)
|
||||
}
|
||||
}
|
||||
6
src/components/SlackAuthLink/SlackAuthLink.scss
Normal file
6
src/components/SlackAuthLink/SlackAuthLink.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.link {
|
||||
text-decoration: none;
|
||||
background: no-repeat left/10% url(./assets/slack_icon.svg);
|
||||
padding: 5px 0 4px 36px;
|
||||
font-size: 1.4em;
|
||||
}
|
||||
20
src/components/SlackAuthLink/assets/slack_icon.svg
Normal file
20
src/components/SlackAuthLink/assets/slack_icon.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="148px" height="147px" viewBox="0 0 148 147" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<defs></defs>
|
||||
<g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M12.997,77.78 C7.503,77.822 2.849,74.548 1.133,69.438 C1.067,69.24 1.01,69.048 0.955,68.855 C-0.915,62.311 2.711,55.465 9.21,53.273 L113.45,18.35 C114.717,17.987 115.993,17.802 117.257,17.794 C122.897,17.75 127.679,21.096 129.437,26.314 L129.593,26.818 C131.543,33.634 126.698,39.718 120.893,41.668 C120.889,41.671 119.833,42.028 17.231,77.059 C15.844,77.53 14.421,77.768 12.997,77.78 L12.997,77.78 L12.997,77.78 Z" fill="#70CADB"></path>
|
||||
<path d="M30.372,129.045 C24.835,129.085 20.165,125.857 18.469,120.82 C18.405,120.628 18.344,120.435 18.289,120.241 C16.393,113.619 20.015,106.701 26.536,104.506 L130.78,69.263 C132.127,68.813 133.518,68.583 134.917,68.57 C140.469,68.528 145.347,71.92 147.068,77.014 L147.228,77.544 C148.235,81.065 147.64,85.022 145.638,88.145 C144.146,90.467 139.44,92.511 139.44,92.511 L34.8,128.29 C33.342,128.777 31.855,129.034 30.372,129.047 L30.372,129.045 L30.372,129.045 Z" fill="#E01765"></path>
|
||||
<path d="M117.148,129.268 C111.588,129.311 106.665,125.803 104.893,120.545 L70.103,17.205 L69.929,16.625 C68.044,10.035 71.669,3.161 78.166,0.971 C79.466,0.534 80.81,0.306 82.163,0.294 C84.173,0.279 86.118,0.732 87.95,1.637 C91.013,3.162 93.304,5.787 94.399,9.029 L129.186,112.36 L129.287,112.692 C131.241,119.534 127.624,126.412 121.127,128.602 C119.84,129.031 118.5,129.256 117.148,129.268 L117.148,129.268 L117.148,129.268 Z" fill="#E8A723"></path>
|
||||
<path d="M65.435,146.674 C59.875,146.717 54.948,143.209 53.175,137.944 L18.394,34.608 C18.334,34.418 18.274,34.228 18.216,34.033 C16.336,27.445 19.95,20.57 26.445,18.378 C27.74,17.948 29.079,17.721 30.43,17.71 C35.991,17.666 40.915,21.173 42.687,26.433 L77.469,129.773 C77.534,129.953 77.593,130.152 77.646,130.342 C79.53,136.935 75.914,143.814 69.409,146.006 C68.117,146.437 66.78,146.662 65.431,146.673 L65.435,146.673 L65.435,146.674 Z" fill="#3EB890"></path>
|
||||
<path d="M99.997,105.996 L124.255,97.702 L116.325,74.152 L92.039,82.359 L99.997,105.996 L99.997,105.996 Z" fill="#CC2027"></path>
|
||||
<path d="M48.364,123.65 L72.62,115.357 L64.63,91.627 L40.35,99.837 L48.364,123.65 L48.364,123.65 Z" fill="#361238"></path>
|
||||
<path d="M82.727,54.7 L106.987,46.417 L99.15,23.142 L74.845,31.285 L82.727,54.7 L82.727,54.7 Z" fill="#65863A"></path>
|
||||
<path d="M31.088,72.33 L55.348,64.047 L47.415,40.475 L23.11,48.617 L31.088,72.33 L31.088,72.33 Z" fill="#1A937D"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
2
src/components/SlackAuthLink/index.js
Normal file
2
src/components/SlackAuthLink/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import SlackAuthLink from './SlackAuthLink';
|
||||
export default SlackAuthLink;
|
||||
53
src/index.js
53
src/index.js
@@ -1,40 +1,46 @@
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import { Router, Route } from 'react-router';
|
||||
import { Router, Route, IndexRoute } from 'react-router';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
import { routerMiddleware } from 'react-router-redux';
|
||||
import { persistStore, autoRehydrate } from 'redux-persist';
|
||||
import thunkMiddleware from 'redux-thunk';
|
||||
import * as storage from 'redux-storage';
|
||||
import createEngine from 'redux-storage-engine-localstorage';
|
||||
import History from './Utils/History';
|
||||
import createLogger from 'redux-logger';
|
||||
import History from 'utils/History';
|
||||
|
||||
import Auth from './Utils/Auth';
|
||||
import auth from 'utils/auth';
|
||||
|
||||
import reducers from './Reducers';
|
||||
import reducers from 'reducers';
|
||||
|
||||
import App from './Views/App';
|
||||
import Login from './Views/Login';
|
||||
import Dashboard from './Views/Dashboard';
|
||||
import 'utils/base-styles.scss';
|
||||
|
||||
import Home from 'scenes/Home';
|
||||
import App from 'scenes/App';
|
||||
import Dashboard from 'scenes/Dashboard';
|
||||
import SlackAuth from 'scenes/SlackAuth';
|
||||
|
||||
// Redux
|
||||
|
||||
const reducer = storage.reducer(reducers);
|
||||
const engine = createEngine('atlas-store');
|
||||
const storageMiddleware = storage.createMiddleware(engine);
|
||||
const loggerMiddleware = createLogger();
|
||||
const routerMiddlewareWithHistory = routerMiddleware(History);
|
||||
|
||||
const createStoreWithMiddleware = applyMiddleware(storageMiddleware)(createStore);
|
||||
const store = createStoreWithMiddleware(reducer);
|
||||
const createStoreWithMiddleware = (createStore);
|
||||
|
||||
const load = storage.createLoader(engine);
|
||||
load(store);
|
||||
// .then((newState) => console.log('Loaded state:', newState));
|
||||
// .catch(() => console.log('Failed to load previous state'));
|
||||
|
||||
// React router
|
||||
const store = createStore(reducer, applyMiddleware(
|
||||
thunkMiddleware,
|
||||
routerMiddlewareWithHistory,
|
||||
loggerMiddleware,
|
||||
), autoRehydrate());
|
||||
persistStore(store);
|
||||
|
||||
function requireAuth(nextState, replace) {
|
||||
if (!Auth.loggedIn()) {
|
||||
if (!auth.loggedIn()) {
|
||||
replace({
|
||||
pathname: '/login',
|
||||
pathname: '/',
|
||||
state: { nextPathname: nextState.location.pathname },
|
||||
});
|
||||
}
|
||||
@@ -43,7 +49,14 @@ function requireAuth(nextState, replace) {
|
||||
render((
|
||||
<Provider store={store}>
|
||||
<Router history={History}>
|
||||
<Route path="/" component={App} />
|
||||
<Route path="/">
|
||||
<IndexRoute component={Home} />
|
||||
|
||||
<Route path="/dashboard" component={Dashboard} onEnter={ requireAuth } />
|
||||
<Route path="/editor" component={App} />
|
||||
|
||||
<Route path="/auth/slack" component={SlackAuth} />
|
||||
</Route>
|
||||
</Router>
|
||||
</Provider>
|
||||
), document.getElementById('root'));
|
||||
|
||||
62
src/reducers/EditorReducer.js
Normal file
62
src/reducers/EditorReducer.js
Normal file
@@ -0,0 +1,62 @@
|
||||
const defaultTest = `# Welcome to Beautiful Atlas
|
||||
|
||||
This is just a small preview here's what you can do:
|
||||
|
||||
- Write markdown or rich text, you choose
|
||||
- Dont' worry about saving
|
||||
- One document for now
|
||||
- More to come
|
||||
`
|
||||
|
||||
const state = {
|
||||
previousTest: null,
|
||||
text: null,
|
||||
unsavedChanges: false,
|
||||
};
|
||||
|
||||
const text = (state = state, action) => {
|
||||
|
||||
switch (action.type) {
|
||||
case UPDATE_TEXT: {
|
||||
let unsavedChanges = false;
|
||||
if (lastRevision && lastRevision.text !== state.text) {
|
||||
unsavedChanges = true;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
unsavedChanges,
|
||||
text: action.text,
|
||||
};
|
||||
}
|
||||
case ADD_REVISION: {
|
||||
// Create new revision if it differs from the previous one
|
||||
if (!lastRevision || lastRevision.text !== state.text) {
|
||||
const lastId = lastRevision ? lastRevision.id : 0;
|
||||
return {
|
||||
...state,
|
||||
revisions: [
|
||||
...state.revisions,
|
||||
{
|
||||
id: lastId + 1,
|
||||
text: state.text,
|
||||
created_at: action.createdAt,
|
||||
},
|
||||
],
|
||||
unsavedChanges: false,
|
||||
};
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
case REPLACE_TEXT: {
|
||||
const newText = state.text.replace(action.originalText, action.replacedText);
|
||||
|
||||
return {
|
||||
...state,
|
||||
text: newText,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
15
src/reducers/team.js
Normal file
15
src/reducers/team.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { UPDATE_TEAM } from 'actions/TeamActions';
|
||||
|
||||
const team = (state = null, action) => {
|
||||
switch (action.type) {
|
||||
case UPDATE_TEAM: {
|
||||
return {
|
||||
...action.team,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default team;
|
||||
15
src/reducers/user.js
Normal file
15
src/reducers/user.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { UPDATE_USER } from 'actions/UserActions';
|
||||
|
||||
const user = (state = null, action) => {
|
||||
switch (action.type) {
|
||||
case UPDATE_USER: {
|
||||
return {
|
||||
...action.user,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default user;
|
||||
@@ -8,52 +8,28 @@ import styles from './App.scss';
|
||||
import {
|
||||
toggleEditors,
|
||||
addRevision,
|
||||
} from '../../Actions';
|
||||
} from '../../actions';
|
||||
|
||||
import Header from '../../Components/Header';
|
||||
import Dashboard from '../Dashboard';
|
||||
|
||||
import Auth from '../../Utils/Auth';
|
||||
import Header from '../../components/Header';
|
||||
import Editor from '../../components/Editor';
|
||||
|
||||
class App extends Component {
|
||||
static propTypes = {
|
||||
children: React.PropTypes.element,
|
||||
activeEditors: React.PropTypes.array.isRequired,
|
||||
toggleEditors: React.PropTypes.func.isRequired,
|
||||
addRevision: React.PropTypes.func.isRequired,
|
||||
unsavedChanges: React.PropTypes.bool.isRequired,
|
||||
}
|
||||
|
||||
state = {
|
||||
loggedIn: Auth.loggedIn(),
|
||||
}
|
||||
|
||||
componentWillMount = () => {
|
||||
Auth.onChange = this.updateAuth;
|
||||
}
|
||||
|
||||
updateAuth = (loggedIn) => {
|
||||
this.setState({
|
||||
loggedIn,
|
||||
});
|
||||
}
|
||||
|
||||
logout = () => {
|
||||
// TODO: Replace with Redux actions
|
||||
Auth.logout();
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<Header
|
||||
activeEditors={this.props.activeEditors}
|
||||
toggleEditors={this.props.toggleEditors}
|
||||
addRevision={this.props.addRevision}
|
||||
unsavedChanges={this.props.unsavedChanges}
|
||||
/>
|
||||
<div className={ styles.content }>
|
||||
<Dashboard />
|
||||
<Editor />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -69,9 +45,6 @@ const mapStateToProps = (state) => {
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return {
|
||||
toggleEditors: (toggledEditor) => {
|
||||
dispatch(toggleEditors(toggledEditor));
|
||||
},
|
||||
addRevision: () => {
|
||||
dispatch(addRevision());
|
||||
},
|
||||
@@ -84,9 +57,3 @@ App = connect(
|
||||
)(App);
|
||||
|
||||
export default App;
|
||||
|
||||
// {this.state.loggedIn ? (
|
||||
// <a href="#" onClick={this.logout}>Logout</a>
|
||||
// ) : (
|
||||
// <Link to="/login">Login</Link>
|
||||
// )}
|
||||
33
src/scenes/Dashboard/Dashboard.js
Normal file
33
src/scenes/Dashboard/Dashboard.js
Normal file
@@ -0,0 +1,33 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { replace } from 'react-router-redux';
|
||||
|
||||
import { client } from 'utils/ApiClient';
|
||||
|
||||
import Layout from 'components/Layout';
|
||||
import styles from './Dashboard.scss';
|
||||
|
||||
class Dashboard extends React.Component {
|
||||
static propTypes = {
|
||||
replace: React.PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Layout
|
||||
header={<div>header!</div>}
|
||||
>
|
||||
holla!
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return bindActionCreators({ replace }, dispatch)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null, mapDispatchToProps
|
||||
)(Dashboard);
|
||||
0
src/scenes/Dashboard/Dashboard.scss
Normal file
0
src/scenes/Dashboard/Dashboard.scss
Normal file
@@ -1,2 +1,2 @@
|
||||
import Dashboard from './Dashboard';
|
||||
export default Dashboard;
|
||||
export default Dashboard;
|
||||
63
src/scenes/Home/Home.js
Normal file
63
src/scenes/Home/Home.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
import { replace } from 'react-router-redux';
|
||||
|
||||
import auth from '../../utils/auth';
|
||||
|
||||
import SlackAuthLink from '../../components/SlackAuthLink';
|
||||
|
||||
import styles from './Home.scss';
|
||||
|
||||
class Home extends React.Component {
|
||||
static propTypes = {
|
||||
replace: React.PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
componentDidMount = () => {
|
||||
if (auth.loggedIn()) {
|
||||
this.props.replace('/dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={ styles.container }>
|
||||
<div className={ styles.content }>
|
||||
<div className={ styles.intro }>
|
||||
<p>
|
||||
Hi there,
|
||||
</p>
|
||||
<p>
|
||||
We're building the best place for engineers, designers and teams to
|
||||
share ideas, tell stories and build knowledge.
|
||||
</p>
|
||||
<p>
|
||||
<strong>**Atlas**</strong> can start as a wiki, but it's really
|
||||
up to you what you want to make of it:
|
||||
</p>
|
||||
<p>
|
||||
- Write documentation in <i>_markdown_</i><br/>
|
||||
- Build a blog around the API<br/>
|
||||
- Hack the frontend for your needs (coming!)<br/>
|
||||
</p>
|
||||
<p>
|
||||
We're just getting started.
|
||||
</p>
|
||||
</div>
|
||||
<div className={ styles.action }>
|
||||
<SlackAuthLink />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = (dispatch) => {
|
||||
return bindActionCreators({ replace }, dispatch)
|
||||
}
|
||||
|
||||
export default connect(
|
||||
null, mapDispatchToProps
|
||||
)(Home);
|
||||
17
src/scenes/Home/Home.scss
Normal file
17
src/scenes/Home/Home.scss
Normal file
@@ -0,0 +1,17 @@
|
||||
.container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 40px;
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
.intro {
|
||||
font-family: "Atlas Typewriter", Monaco, monospace;
|
||||
font-size: 1.4em;
|
||||
line-height: 1.6em;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
26
src/scenes/Home/animation.js
Normal file
26
src/scenes/Home/animation.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import { Frame } from 'react-keyframes';
|
||||
|
||||
let frames = [];
|
||||
const p = (node) => frames.push(node);
|
||||
const E = (props) => {
|
||||
return (<Frame duration={props.duration || 300} component='div'>{ props.children }</Frame>);
|
||||
};
|
||||
|
||||
const line1 = (<p>Hi there,</p>);
|
||||
const line2 = (<p>We're excited to share what we’re building.</p>);
|
||||
const line3 = (<p>We <strong>**love**</strong> Markdown,</p>);
|
||||
const line4 = (<p>but we also get that it's not for everyone.</p>);
|
||||
const line5 = (<p>Together with you,</p>);
|
||||
const line6 = (<p>we want to build the best place to</p>);
|
||||
const line7 = (<p>share ideas,</p>);
|
||||
const line8 = (<p>tell stories,</p>);
|
||||
const line9 = (<p>and build knowledge.</p>);
|
||||
const line10 = (<p>We're just getting started.</p>);
|
||||
const line11 = (<p>Welcome to Beautiful Atlas.</p>);
|
||||
|
||||
p(<E>{line1}{line2}{line3}{line4}{line5}{line6}{line7}{line8}{line9}{line10}{line11}</E>);
|
||||
|
||||
// Hmms leaving this here for now, would be nice to something
|
||||
|
||||
export default frames;
|
||||
2
src/scenes/Home/index.js
Normal file
2
src/scenes/Home/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import Home from './Home';
|
||||
export default Home;
|
||||
35
src/scenes/SlackAuth/SlackAuth.js
Normal file
35
src/scenes/SlackAuth/SlackAuth.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { bindActionCreators } from 'redux';
|
||||
|
||||
import { slackAuthAsync } from '../../actions/SlackAuthAction';
|
||||
|
||||
import { client } from '../../utils/ApiClient';
|
||||
|
||||
class SlackAuth extends React.Component {
|
||||
componentDidMount = () => {
|
||||
const { query } = this.props.location
|
||||
|
||||
// Validate OAuth2 state param
|
||||
if (localStorage.oauthState != query.state) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.props.slackAuthAsync(query.code);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>Loading...</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispactcToProps = (dispatch) => {
|
||||
return bindActionCreators({ slackAuthAsync }, dispatch);
|
||||
};
|
||||
|
||||
export default connect(
|
||||
null,
|
||||
mapDispactcToProps
|
||||
)(SlackAuth);
|
||||
2
src/scenes/SlackAuth/index.js
Normal file
2
src/scenes/SlackAuth/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
import SlackAuth from './SlackAuth';
|
||||
export default SlackAuth;
|
||||
9
src/utils/actions.js
Normal file
9
src/utils/actions.js
Normal file
@@ -0,0 +1,9 @@
|
||||
export default (type, ...argNames) => {
|
||||
return function(...args) {
|
||||
let action = { type }
|
||||
argNames.forEach((arg, index) => {
|
||||
action[argNames[index]] = args[index]
|
||||
})
|
||||
return action
|
||||
}
|
||||
}
|
||||
23
src/utils/base-styles.scss
Normal file
23
src/utils/base-styles.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body, .viewport {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
html, body {
|
||||
font-family: 'Atlas Grotesk', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0C77F8;
|
||||
}
|
||||
|
||||
:global {
|
||||
.Codemirror {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user