diff --git a/server/api/atlases.js b/server/api/atlases.js
new file mode 100644
index 00000000..747f074c
--- /dev/null
+++ b/server/api/atlases.js
@@ -0,0 +1,52 @@
+import Router from 'koa-router';
+import httpErrors from 'http-errors';
+
+import auth from './authentication';
+import pagination from './middlewares/pagination';
+import { presentAtlas } from '../presenters';
+import { Team, Atlas } from '../models';
+
+const router = new Router();
+
+router.post('atlases.info', auth(), async (ctx) => {
+ let { id } = ctx.request.body;
+ ctx.assertPresent(id, 'id is required');
+
+ const team = await ctx.state.user.getTeam();
+ const atlas = await Atlas.findOne({
+ where: {
+ id: id,
+ teamId: team.id,
+ },
+ });
+
+ if (!atlas) throw httpErrors.NotFound();
+
+ ctx.body = {
+ data: presentAtlas(atlas),
+ };
+});
+
+
+router.post('atlases.list', auth(), pagination(), async (ctx) => {
+ let { teamId } = ctx.request.body;
+ ctx.assertPresent(teamId, 'teamId is required');
+
+ const team = await ctx.state.user.getTeam();
+ const atlases = await Atlas.findAll({
+ where: {
+ teamId: teamId,
+ },
+ offset: ctx.state.pagination.offset,
+ limit: ctx.state.pagination.limit,
+ });
+
+ ctx.body = {
+ pagination: ctx.state.pagination,
+ data: atlases.map((atlas) => {
+ return presentAtlas(atlas);
+ })
+ };
+});
+
+export default router;
\ No newline at end of file
diff --git a/server/api/index.js b/server/api/index.js
index 14b55ed3..3e0e9d07 100644
--- a/server/api/index.js
+++ b/server/api/index.js
@@ -6,6 +6,7 @@ import Sequelize from 'sequelize';
import auth from './auth';
import user from './user';
+import atlases from './atlases';
import validation from './validation';
@@ -42,6 +43,7 @@ api.use(validation());
router.use('/', auth.routes());
router.use('/', user.routes());
+router.use('/', atlases.routes());
// Router is embedded in a Koa application wrapper, because koa-router does not
// allow middleware to catch any routes which were not explicitly defined.
diff --git a/server/models/Atlas.js b/server/models/Atlas.js
index 4476c403..a96ba6ca 100644
--- a/server/models/Atlas.js
+++ b/server/models/Atlas.js
@@ -4,9 +4,19 @@ import {
} from '../sequelize';
import Team from './Team';
+const allowedAtlasTypes = [['atlas', 'journal']];
+
const Atlas = sequelize.define('atlas', {
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
name: DataTypes.STRING,
+ description: DataTypes.STRING,
+ type: { type: DataTypes.STRING, validate: { isIn: allowedAtlasTypes }},
+}, {
+ instanceMethods: {
+ getRecentDocuments() {
+ return [];
+ },
+ },
});
Atlas.belongsTo(Team);
diff --git a/server/models/User.js b/server/models/User.js
index 40964152..9b8da5f4 100644
--- a/server/models/User.js
+++ b/server/models/User.js
@@ -23,6 +23,9 @@ const User = sequelize.define('user', {
getJwtToken() {
return JWT.sign({ id: this.id }, this.jwtSecret);
},
+ async getTeam() {
+ return this.team;
+ }
},
indexes: [
{
diff --git a/server/presenters.js b/server/presenters.js
index e3abf59a..a9baab29 100644
--- a/server/presenters.js
+++ b/server/presenters.js
@@ -14,3 +14,13 @@ export function presentTeam(team) {
name: team.name,
};
}
+
+export function presentAtlas(atlas) {
+ return {
+ id: atlas.id,
+ name: atlas.name,
+ description: atlas.description,
+ type: atlas.type,
+ recentDocuments: atlas.getRecentDocuments(),
+ }
+}
\ No newline at end of file
diff --git a/src/Reducers/index.js b/src/Reducers/index.js
index c378891b..f8336382 100644
--- a/src/Reducers/index.js
+++ b/src/Reducers/index.js
@@ -106,6 +106,7 @@ const text = (state = textDefaultState, action) => {
import team from './team';
import user from './user';
+import atlases from './atlases';
export default combineReducers({
activeEditors,
@@ -113,4 +114,5 @@ export default combineReducers({
text,
user,
team,
+ atlases,
});
diff --git a/src/actions/AtlasActions.js b/src/actions/AtlasActions.js
new file mode 100644
index 00000000..346decbf
--- /dev/null
+++ b/src/actions/AtlasActions.js
@@ -0,0 +1,52 @@
+import makeActionCreator from '../utils/actions';
+import { client } from 'utils/ApiClient';
+
+export const FETCH_ATLASES_PENDING = 'FETCH_ATLASES_PENDING';
+export const FETCH_ATLASES_SUCCESS = 'FETCH_ATLASES_SUCCESS';
+export const FETCH_ATLASES_FAILURE = 'FETCH_ATLASES_FAILURE';
+
+const fetchAtlasesPending = makeActionCreator(FETCH_ATLASES_PENDING);
+const fetchAtlasesSuccess = makeActionCreator(FETCH_ATLASES_SUCCESS, 'items', 'pagination');
+const fetchAtlasesFailure = makeActionCreator(FETCH_ATLASES_FAILURE, 'error');
+
+export function fetchAtlasesAsync(teamId) {
+ return (dispatch) => {
+ dispatch(fetchAtlasesPending());
+
+ client.post('/atlases.list', {
+ teamId: teamId,
+ })
+ .then(data => {
+ dispatch(fetchAtlasesSuccess(data.data, data.pagination));
+ })
+ .catch((err) => {
+ dispatch(fetchAtlasesFailure(err));
+ })
+ };
+};
+
+
+
+export const FETCH_ATLAS_PENDING = 'FETCH_ATLAS_PENDING';
+export const FETCH_ATLAS_SUCCESS = 'FETCH_ATLAS_SUCCESS';
+export const FETCH_ATLAS_FAILURE = 'FETCH_ATLAS_FAILURE';
+
+const fetchAtlasPending = makeActionCreator(FETCH_ATLAS_PENDING);
+const fetchAtlasSuccess = makeActionCreator(FETCH_ATLAS_SUCCESS, 'data');
+const fetchAtlasFailure = makeActionCreator(FETCH_ATLAS_FAILURE, 'error');
+
+export function fetchAtlasAsync(atlasId) {
+ return (dispatch) => {
+ dispatch(fetchAtlasPending());
+
+ client.post('/atlases.info', {
+ id: atlasId,
+ })
+ .then(data => {
+ dispatch(fetchAtlasSuccess(data.data,));
+ })
+ .catch((err) => {
+ dispatch(fetchAtlasFailure(err));
+ })
+ };
+};
\ No newline at end of file
diff --git a/src/components/AtlasPreview/AtlasPreview.js b/src/components/AtlasPreview/AtlasPreview.js
new file mode 100644
index 00000000..1ebaa966
--- /dev/null
+++ b/src/components/AtlasPreview/AtlasPreview.js
@@ -0,0 +1,23 @@
+import React from 'react';
+import Link from 'react-router/lib/Link';
+
+import styles from './AtlasPreview.scss';
+import classNames from 'classnames/bind';
+const cx = classNames.bind(styles);
+
+class AtlasPreview extends React.Component {
+ static propTypes = {
+ data: React.PropTypes.object.isRequired,
+ }
+
+ render() {
+ return (
+
+
{ this.props.data.name }
+
No documents. Why not create one?
+
+ );
+ }
+};
+
+export default AtlasPreview;
\ No newline at end of file
diff --git a/src/components/AtlasPreview/AtlasPreview.scss b/src/components/AtlasPreview/AtlasPreview.scss
new file mode 100644
index 00000000..8a464b83
--- /dev/null
+++ b/src/components/AtlasPreview/AtlasPreview.scss
@@ -0,0 +1,6 @@
+@import '../../utils/constants.scss';
+
+.atlasLink {
+ text-decoration: none;
+ color: $textColor;
+}
\ No newline at end of file
diff --git a/src/components/AtlasPreview/index.js b/src/components/AtlasPreview/index.js
new file mode 100644
index 00000000..6b1ecbf5
--- /dev/null
+++ b/src/components/AtlasPreview/index.js
@@ -0,0 +1,2 @@
+import AtlasPreview from './AtlasPreview';
+export default AtlasPreview;
\ No newline at end of file
diff --git a/src/components/CenteredContent/CenteredContent.js b/src/components/CenteredContent/CenteredContent.js
new file mode 100644
index 00000000..71dfdad0
--- /dev/null
+++ b/src/components/CenteredContent/CenteredContent.js
@@ -0,0 +1,27 @@
+import React from 'react';
+
+import styles from './CenteredContent.scss';
+
+const CenteredContent = (props) => {
+ const style = {
+ maxWidth: props.maxWidth,
+ ...props.style,
+ };
+
+ return (
+
+ { props.children }
+
+ );
+};
+
+CenteredContent.defaultProps = {
+ maxWidth: '600px',
+};
+
+CenteredContent.propTypes = {
+ children: React.PropTypes.node.isRequired,
+ style: React.PropTypes.object,
+};
+
+export default CenteredContent;
\ No newline at end of file
diff --git a/src/components/CenteredContent/CenteredContent.scss b/src/components/CenteredContent/CenteredContent.scss
new file mode 100644
index 00000000..1e74499d
--- /dev/null
+++ b/src/components/CenteredContent/CenteredContent.scss
@@ -0,0 +1,5 @@
+.content {
+ display: flex;
+ flex: 1;
+ margin: 40px 20px;
+}
\ No newline at end of file
diff --git a/src/components/CenteredContent/index.js b/src/components/CenteredContent/index.js
new file mode 100644
index 00000000..1e05cad5
--- /dev/null
+++ b/src/components/CenteredContent/index.js
@@ -0,0 +1,2 @@
+import CenteredContent from './CenteredContent';
+export default CenteredContent;
\ No newline at end of file
diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js
index e0e07f5e..24c1408d 100644
--- a/src/components/Layout/Layout.js
+++ b/src/components/Layout/Layout.js
@@ -1,5 +1,6 @@
import React from 'react';
import { connect } from 'react-redux';
+import Link from 'react-router/lib/Link';
import HeaderMenu from './components/HeaderMenu';
@@ -14,7 +15,9 @@ class Layout extends React.Component {
return (
-
Coinbase
+
+ { this.props.teamName }
+
@@ -29,6 +32,7 @@ class Layout extends React.Component {
const mapStateToProps = (state) => {
return {
+ teamName: state.team ? state.team.name : null,
avatarUrl: state.user ? state.user.avatarUrl : null,
}
};
diff --git a/src/components/Layout/Layout.scss b/src/components/Layout/Layout.scss
index bfa06a59..b5985188 100644
--- a/src/components/Layout/Layout.scss
+++ b/src/components/Layout/Layout.scss
@@ -1,3 +1,5 @@
+@import '../../utils/constants.scss';
+
.container {
display: flex;
flex: 1;
@@ -16,9 +18,11 @@
border-bottom: 1px solid #eee;
}
-.teamName {
+.teamName a {
font-family: 'Atlas Grotesk';
font-weight: bold;
+ color: $textColor;
+ text-decoration: none;
}
.content {
diff --git a/src/index.js b/src/index.js
index 466f829d..c2b0bbd8 100644
--- a/src/index.js
+++ b/src/index.js
@@ -22,6 +22,7 @@ import 'fonts/atlas/atlas.css';
import Home from 'scenes/Home';
// import App from 'scenes/App';
import Dashboard from 'scenes/Dashboard';
+import Atlas from 'scenes/Atlas';
import SlackAuth from 'scenes/SlackAuth';
// Redux
@@ -47,12 +48,10 @@ persistStore(store, {
-
-
-
+
+
-
+
@@ -69,4 +68,3 @@ function requireAuth(nextState, replace) {
});
}
}
-
diff --git a/src/reducers/atlases.js b/src/reducers/atlases.js
new file mode 100644
index 00000000..c46317ee
--- /dev/null
+++ b/src/reducers/atlases.js
@@ -0,0 +1,41 @@
+import {
+ FETCH_ATLASES_PENDING,
+ FETCH_ATLASES_SUCCESS,
+ FETCH_ATLASES_FAILURE,
+} from 'actions/AtlasActions';
+
+const initialState = {
+ items: [],
+ pagination: null,
+ isLoading: false,
+}
+
+const atlases = (state = initialState, action) => {
+ switch (action.type) {
+ case FETCH_ATLASES_PENDING: {
+ return {
+ ...state,
+ isLoading: true,
+ };
+ }
+ case FETCH_ATLASES_SUCCESS: {
+ return {
+ ...state,
+ items: action.items,
+ pagination: action.pagination,
+ isLoading: false,
+ };
+ }
+ case FETCH_ATLASES_FAILURE: {
+ return {
+ ...state,
+ isLoading: false,
+ error: action.error,
+ };
+ }
+ default:
+ return state;
+ }
+};
+
+export default atlases;
\ No newline at end of file
diff --git a/src/scenes/Atlas/Atlas.js b/src/scenes/Atlas/Atlas.js
new file mode 100644
index 00000000..b50b6476
--- /dev/null
+++ b/src/scenes/Atlas/Atlas.js
@@ -0,0 +1,85 @@
+import React from 'react';
+import { connect } from 'react-redux';
+import { bindActionCreators } from 'redux';
+import { replace } from 'react-router-redux';
+import { fetchAtlasAsync } from 'actions/AtlasActions';
+
+// Temp
+import { client } from 'utils/ApiClient';
+
+import Layout from 'components/Layout';
+import AtlasPreviewLoading from 'components/AtlasPreviewLoading';
+import CenteredContent from 'components/CenteredContent';
+
+import styles from './Atlas.scss';
+
+class Atlas extends React.Component {
+ static propTypes = {
+ atlas: React.PropTypes.object,
+ }
+
+ state = {
+ isLoading: true,
+ data: null,
+ }
+
+ componentDidMount = () => {
+ const { id } = this.props.params;
+
+ // this.props.fetchAtlasAsync(id);
+
+ // Temp before breaking out redux store
+ client.post('/atlases.info', {
+ id: id,
+ })
+ .then(data => {
+ this.setState({
+ isLoading: false,
+ data: data.data
+ });
+ })
+ }
+
+ render() {
+ const data = this.state.data;
+
+ return (
+
+
+ { this.state.isLoading ? (
+
+ ) : (
+
+
+
{ data.name }
+
+ { data.description }
+
+
+
+
+
+ ) }
+
+
+ );
+ }
+}
+
+const mapStateToProps = (state) => {
+ return {
+ isLoading: state.atlases.isLoading,
+ }
+};
+
+const mapDispatchToProps = (dispatch) => {
+ return bindActionCreators({
+ replace,
+ fetchAtlasAsync,
+ }, dispatch)
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps
+)(Atlas);
diff --git a/src/scenes/Atlas/Atlas.scss b/src/scenes/Atlas/Atlas.scss
new file mode 100644
index 00000000..547b0d7b
--- /dev/null
+++ b/src/scenes/Atlas/Atlas.scss
@@ -0,0 +1,31 @@
+.container {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+}
+
+.atlasDetails {
+ display: flex;
+ flex: 1;
+ flex-direction: column;
+
+ blockquote {
+ padding: 0;
+ margin: 0 0 20px 0;
+ font-style: italic;
+ }
+}
+
+.divider {
+ display: flex;
+ flex: 1;
+ justify-content: center;
+
+ span {
+ display: flex;
+ width: 50%;
+ margin: 20px 0;
+
+ border-bottom: 1px solid #eee;
+ }
+}
\ No newline at end of file
diff --git a/src/scenes/Atlas/index.js b/src/scenes/Atlas/index.js
new file mode 100644
index 00000000..aa7713c8
--- /dev/null
+++ b/src/scenes/Atlas/index.js
@@ -0,0 +1,2 @@
+import Atlas from './Atlas';
+export default Atlas;
\ No newline at end of file
diff --git a/src/scenes/Dashboard/Dashboard.js b/src/scenes/Dashboard/Dashboard.js
index e8d1403b..a6c9ade3 100644
--- a/src/scenes/Dashboard/Dashboard.js
+++ b/src/scenes/Dashboard/Dashboard.js
@@ -1,37 +1,53 @@
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 { fetchAtlasesAsync } from 'actions/AtlasActions';
import Layout from 'components/Layout';
+import AtlasPreview from 'components/AtlasPreview';
import AtlasPreviewLoading from 'components/AtlasPreviewLoading';
+import CenteredContent from 'components/CenteredContent';
import styles from './Dashboard.scss';
class Dashboard extends React.Component {
static propTypes = {
- replace: React.PropTypes.func.isRequired,
+ }
+
+ componentDidMount = () => {
+ this.props.fetchAtlasesAsync(this.props.teamId);
}
render() {
return (
-
header!}
- >
-
+
+
+ { this.props.isLoading ? (
+
+ ) : this.props.items.map((item) => {
+ return ();
+ }) }
+
);
}
}
+const mapStateToProps = (state) => {
+ return {
+ teamId: state.team ? state.team.id : null,
+ isLoading: state.atlases.isLoading,
+ items: state.atlases.items,
+ }
+};
+
const mapDispatchToProps = (dispatch) => {
- return bindActionCreators({ replace }, dispatch)
+ return bindActionCreators({
+ fetchAtlasesAsync,
+ }, dispatch)
}
export default connect(
- null, mapDispatchToProps
+ mapStateToProps,
+ mapDispatchToProps
)(Dashboard);
diff --git a/src/scenes/Dashboard/Dashboard.scss b/src/scenes/Dashboard/Dashboard.scss
index a2dad1f8..e69de29b 100644
--- a/src/scenes/Dashboard/Dashboard.scss
+++ b/src/scenes/Dashboard/Dashboard.scss
@@ -1,7 +0,0 @@
-.content {
- display: flex;
- flex: 1;
- max-width: 600px;
-
- margin: 40px 20px;
-}
\ No newline at end of file