@@ -129,43 +128,50 @@ type Props = {
{auth.authenticated &&
user &&
+ team &&
-
-
- Atlas
-
- }>
-
- Settings
-
-
- Keyboard shortcuts
-
-
- API
-
-
- Logout
-
-
-
+
+
+
+ }
+ >
+
+ Settings
+
+
+ Keyboard shortcuts
+
+
+ API
+
+
+ Logout
+
+
- Search
-
-
- Home
- Starred
+
+ Home
+
+
+ Search
+
+
+ Starred
+
{collections.active
?
-
+
:
-
+
}
{collections.active
? (props.editMode ? 0 : layout.sidebarWidth)};
+ transition: margin-left 200ms ease-in-out;
`;
const MenuLink = styled(Link)`
color: ${color.text};
`;
-const Content = styled(Flex)`
- overflow: scroll;
- position: absolute;
+const Sidebar = styled(Flex)`
+ position: fixed;
top: 0;
bottom: 0;
- right: 0;
- left: ${props => (props.editMode ? 0 : layout.sidebarWidth)};
- transition: left 200ms ease-in-out;
-`;
-
-const Sidebar = styled(Flex)`
+ left: ${props => (props.editMode ? `-${layout.sidebarWidth}` : 0)};
width: ${layout.sidebarWidth};
- margin-left: ${props => (props.editMode ? `-${layout.sidebarWidth}` : 0)};
- background: rgba(250, 251, 252, 0.71);
- border-right: 1px solid #eceff3;
- transition: margin-left 200ms ease-in-out;
-`;
-
-const Header = styled(Flex)`
- flex-shrink: 0;
- padding: ${layout.padding};
- padding-bottom: 10px;
+ background: ${color.smoke};
+ transition: left 200ms ease-in-out;
`;
const LinkSection = styled(Flex)`
flex-direction: column;
- padding: 10px 0;
+ margin: 24px 0;
position: relative;
`;
diff --git a/frontend/components/Layout/components/HeaderAction/HeaderAction.js b/frontend/components/Layout/components/HeaderAction/HeaderAction.js
deleted file mode 100644
index c49806c4a..000000000
--- a/frontend/components/Layout/components/HeaderAction/HeaderAction.js
+++ /dev/null
@@ -1,16 +0,0 @@
-// @flow
-import React from 'react';
-
-import styles from './HeaderAction.scss';
-
-type Props = { onClick?: ?Function, children?: ?React.Element };
-
-const HeaderAction = (props: Props) => {
- return (
-
- {props.children}
-
- );
-};
-
-export default HeaderAction;
diff --git a/frontend/components/Layout/components/HeaderAction/HeaderAction.scss b/frontend/components/Layout/components/HeaderAction/HeaderAction.scss
deleted file mode 100644
index cc5013907..000000000
--- a/frontend/components/Layout/components/HeaderAction/HeaderAction.scss
+++ /dev/null
@@ -1,11 +0,0 @@
-.container {
- display: flex;
- justify-content: center;
- align-items: center;
- min-height: 43px;
- padding: 0 0.5rem;
-
- a {
- color: #171B35;
- }
-}
diff --git a/frontend/components/Layout/components/HeaderAction/index.js b/frontend/components/Layout/components/HeaderAction/index.js
deleted file mode 100644
index e231db11a..000000000
--- a/frontend/components/Layout/components/HeaderAction/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// @flow
-import HeaderAction from './HeaderAction';
-export default HeaderAction;
diff --git a/frontend/components/Layout/components/HeaderBlock.js b/frontend/components/Layout/components/HeaderBlock.js
new file mode 100644
index 000000000..1038738e4
--- /dev/null
+++ b/frontend/components/Layout/components/HeaderBlock.js
@@ -0,0 +1,61 @@
+// @flow
+import React from 'react';
+import styled from 'styled-components';
+import { color, layout } from 'styles/constants';
+import type { User, Team } from 'types';
+import Flex from 'components/Flex';
+
+type Props = {
+ user: User,
+ team: Team,
+ children?: React$Element,
+};
+
+function HeaderBlock({ user, team, children }: Props) {
+ return (
+
+
+ {team.name}
+ {user.name}
+
+ {children}
+
+ );
+}
+
+const UserName = styled.div`
+ font-size: 13px;
+`;
+
+const TeamName = styled.div`
+ font-family: 'Atlas Grotesk';
+ font-weight: bold;
+ color: ${color.text};
+ text-decoration: none;
+ font-size: 16px;
+`;
+
+const Header = styled(Flex)`
+ flex-shrink: 0;
+ padding: ${layout.padding};
+ position: relative;
+ cursor: pointer;
+ width: 100%;
+
+ &:active,
+ &:hover {
+ background: rgba(0,0,0,.05);
+ }
+
+ &::after {
+ content: "";
+ left: ${layout.hpadding};
+ right: ${layout.hpadding};
+ background: rgba(0,0,0,.075);
+ height: 1px;
+ position: absolute;
+ bottom: 0;
+ }
+`;
+
+export default HeaderBlock;
diff --git a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js
index 5f46528e3..9d1cefdda 100644
--- a/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js
+++ b/frontend/components/Layout/components/SidebarCollection/SidebarCollection.js
@@ -3,7 +3,7 @@ import React from 'react';
import { observer } from 'mobx-react';
import Flex from 'components/Flex';
import styled from 'styled-components';
-import { layout } from 'styles/constants';
+import { color, layout } from 'styles/constants';
import SidebarLink from '../SidebarLink';
import DropToImport from 'components/DropToImport';
@@ -18,8 +18,8 @@ type Props = {
};
const activeStyle = {
- color: '#000',
- background: '#E1E1E1',
+ color: color.black,
+ background: color.slateDark,
};
@observer class SidebarCollection extends React.Component {
@@ -72,7 +72,7 @@ const Header = styled(Flex)`
font-size: 11px;
font-weight: 500;
text-transform: uppercase;
- color: #9FA6AB;
+ color: ${color.slate};
letter-spacing: 0.04em;
padding: 0 ${layout.hpadding};
`;
diff --git a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js
index c672ae420..9318e4370 100644
--- a/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js
+++ b/frontend/components/Layout/components/SidebarCollectionList/SidebarCollectionList.js
@@ -3,7 +3,7 @@ import React from 'react';
import { observer, inject } from 'mobx-react';
import Flex from 'components/Flex';
import styled from 'styled-components';
-import { layout } from 'styles/constants';
+import { color, layout, fontWeight } from 'styles/constants';
import SidebarLink from '../SidebarLink';
import DropToImport from 'components/DropToImport';
@@ -16,8 +16,8 @@ type Props = {
};
const activeStyle = {
- color: '#000',
- background: '#E1E1E1',
+ color: color.black,
+ background: color.slateDark,
};
const SidebarCollectionList = observer(({ history, collections }: Props) => {
@@ -42,9 +42,9 @@ const SidebarCollectionList = observer(({ history, collections }: Props) => {
const Header = styled(Flex)`
font-size: 11px;
- font-weight: 500;
+ font-weight: ${fontWeight.semiBold};
text-transform: uppercase;
- color: #9FA6AB;
+ color: ${color.slate};
letter-spacing: 0.04em;
padding: 0 ${layout.hpadding};
`;
diff --git a/frontend/components/Layout/components/SidebarLink/SidebarLink.js b/frontend/components/Layout/components/SidebarLink/SidebarLink.js
index 7df81e86c..cb6f2b0e1 100644
--- a/frontend/components/Layout/components/SidebarLink/SidebarLink.js
+++ b/frontend/components/Layout/components/SidebarLink/SidebarLink.js
@@ -1,25 +1,28 @@
// @flow
import React from 'react';
import { NavLink } from 'react-router-dom';
-import { layout, color } from 'styles/constants';
-import { darken } from 'polished';
+import { layout, color, fontWeight } from 'styles/constants';
import styled from 'styled-components';
const activeStyle = {
- color: '#000000',
+ color: color.black,
+ fontWeight: fontWeight.semiBold,
};
function SidebarLink(props: Object) {
- return ;
+ return ;
}
const StyledNavLink = styled(NavLink)`
display: block;
- padding: 5px ${layout.hpadding};
+ overflow: hidden;
+ text-overflow: ellipsis;
+ margin: 5px ${layout.hpadding};
color: ${color.slateDark};
+ font-size: 15px;
&:hover {
- color: ${darken(0.1, color.slateDark)};
+ color: ${color.text};
}
`;
diff --git a/frontend/components/Layout/index.js b/frontend/components/Layout/index.js
index 38c7a426f..fb7b105d1 100644
--- a/frontend/components/Layout/index.js
+++ b/frontend/components/Layout/index.js
@@ -1,8 +1,7 @@
// @flow
import Layout from './Layout';
import Title from './components/Title';
-import HeaderAction from './components/HeaderAction';
export default Layout;
-export { Title, HeaderAction };
+export { Title };
diff --git a/frontend/components/Modal/Modal.js b/frontend/components/Modal/Modal.js
index 66110f0ec..cde113089 100644
--- a/frontend/components/Modal/Modal.js
+++ b/frontend/components/Modal/Modal.js
@@ -2,9 +2,9 @@
import React from 'react';
import styled from 'styled-components';
import ReactModal from 'react-modal';
+import { color } from 'styles/constants';
import { fadeAndScaleIn } from 'styles/animations';
-
-import CloseIcon from 'components/Icon/CloseIcon';
+import Icon from 'components/Icon';
import Flex from 'components/Flex';
type Props = {
@@ -32,7 +32,7 @@ const Modal = ({
>
{title && {title}
}
-
+
{children}
@@ -68,6 +68,7 @@ const Close = styled.a`
top: 3rem;
right: 3rem;
opacity: .5;
+ color: ${color.text};
&:hover {
opacity: 1;
diff --git a/frontend/index.js b/frontend/index.js
index d47f3774f..b783b4e76 100644
--- a/frontend/index.js
+++ b/frontend/index.js
@@ -51,23 +51,22 @@ type AuthProps = {
};
const Auth = ({ children }: AuthProps) => {
- if (stores.auth.authenticated && stores.auth.team) {
+ if (stores.auth.authenticated && stores.auth.team && stores.auth.user) {
// Only initialize stores once. Kept in global scope
// because otherwise they will get overriden on route
// change
if (!authenticatedStores) {
// Stores for authenticated user
- const user = stores.auth.getUserStore();
- const cache = new CacheStore(user.user.id);
+ const { user, team } = stores.auth;
+ const cache = new CacheStore(user.id);
authenticatedStores = {
- user,
documents: new DocumentsStore({
ui: stores.ui,
cache,
}),
collections: new CollectionsStore({
ui: stores.ui,
- teamId: user.team.id,
+ teamId: team.id,
cache,
}),
};
diff --git a/frontend/scenes/Dashboard/Dashboard.js b/frontend/scenes/Dashboard/Dashboard.js
index da95a0f2b..f8adf4b03 100644
--- a/frontend/scenes/Dashboard/Dashboard.js
+++ b/frontend/scenes/Dashboard/Dashboard.js
@@ -28,8 +28,8 @@ type Props = {
props: Props;
componentDidMount() {
- this.props.documents.fetchAll();
- this.props.documents.fetchRecentlyViewed();
+ this.props.documents.fetchRecentlyModified({ limit: 5 });
+ this.props.documents.fetchRecentlyViewed({ limit: 5 });
}
get showPlaceholder() {
diff --git a/frontend/scenes/Document/Document.js b/frontend/scenes/Document/Document.js
index dbdb66f39..aec4fc2b1 100644
--- a/frontend/scenes/Document/Document.js
+++ b/frontend/scenes/Document/Document.js
@@ -5,7 +5,7 @@ import styled from 'styled-components';
import { observer, inject } from 'mobx-react';
import { withRouter, Prompt } from 'react-router';
import Flex from 'components/Flex';
-import { layout } from 'styles/constants';
+import { color, layout } from 'styles/constants';
import Document from 'models/Document';
import UiStore from 'stores/UiStore';
@@ -15,7 +15,6 @@ import SaveAction from './components/SaveAction';
import LoadingPlaceholder from 'components/LoadingPlaceholder';
import Editor from 'components/Editor';
import DropToImport from 'components/DropToImport';
-import { HeaderAction } from 'components/Layout';
import LoadingIndicator from 'components/LoadingIndicator';
import Collaborators from 'components/Collaborators';
import CenteredContent from 'components/CenteredContent';
@@ -110,6 +109,11 @@ type Props = {
this.props.history.push(url);
};
+ onClickNew = () => {
+ if (!this.document) return;
+ this.props.history.push(`${this.document.collection.url}/new`);
+ };
+
onSave = async (redirect: boolean = false) => {
if (this.document && !this.document.allowSave) return;
let document = this.document;
@@ -230,13 +234,22 @@ type Props = {
}
isNew={!!isNew}
/>
- : Edit}
+ :
+ Edit
+ }
+
+
+ {isEditing
+ ? Cancel
+ : }
+
+ {!isEditing && }
+
+ {!isEditing &&
+
+ New
+ }
- {isEditing &&
-
- Cancel
- }
- {!isEditing && }
@@ -246,6 +259,32 @@ type Props = {
}
}
+const Separator = styled.div`
+ margin-left: 12px;
+ width: 1px;
+ height: 20px;
+ background: ${color.slateLight};
+`;
+
+const HeaderAction = styled(Flex)`
+ justify-content: center;
+ align-items: center;
+ min-height: 43px;
+ color: ${color.text};
+ padding: 0 0 0 14px;
+
+ a,
+ svg {
+ color: ${color.text};
+ opacity: .8;
+ transition: opacity 100ms ease-in-out;
+
+ &:hover {
+ opacity: 1;
+ }
+ }
+`;
+
const DropHere = styled(Flex)`
pointer-events: none;
position: fixed;
diff --git a/frontend/scenes/Document/components/Menu.js b/frontend/scenes/Document/components/Menu.js
index ae315d82c..9bae6f0b8 100644
--- a/frontend/scenes/Document/components/Menu.js
+++ b/frontend/scenes/Document/components/Menu.js
@@ -5,11 +5,8 @@ import get from 'lodash/get';
import { withRouter } from 'react-router-dom';
import { observer } from 'mobx-react';
import Document from 'models/Document';
-import {
- DropdownMenu,
- DropdownMenuItem,
- MoreIcon,
-} from 'components/DropdownMenu';
+import Icon from 'components/Icon';
+import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
type Props = {
history: Object,
@@ -65,7 +62,7 @@ type Props = {
collection.documents.length > 1;
return (
- }>
+ }>
{collection &&
diff --git a/frontend/scenes/Search/components/SearchField/SearchField.js b/frontend/scenes/Search/components/SearchField/SearchField.js
index 785a3a49f..6b2f6577d 100644
--- a/frontend/scenes/Search/components/SearchField/SearchField.js
+++ b/frontend/scenes/Search/components/SearchField/SearchField.js
@@ -1,8 +1,9 @@
// @flow
import React, { Component } from 'react';
+import Icon from 'components/Icon';
import Flex from 'components/Flex';
+import { color } from 'styles/constants';
import styled from 'styled-components';
-import searchImg from 'assets/icons/search.svg';
const Field = styled.input`
width: 100%;
@@ -12,17 +13,10 @@ const Field = styled.input`
outline: none;
border: 0;
- ::-webkit-input-placeholder { color: #ccc; }
- :-moz-placeholder { color: #ccc; }
- ::-moz-placeholder { color: #ccc; }
- :-ms-input-placeholder { color: #ccc; }
-`;
-
-const Icon = styled.img`
- width: 38px;
- margin-bottom: -5px;
- margin-right: 10px;
- opacity: 0.15;
+ ::-webkit-input-placeholder { color: ${color.slate}; }
+ :-moz-placeholder { color: ${color.slate}; }
+ ::-moz-placeholder { color: ${color.slate}; }
+ :-ms-input-placeholder { color: ${color.slate}; }
`;
class SearchField extends Component {
@@ -45,13 +39,18 @@ class SearchField extends Component {
render() {
return (
-
-
+
+
diff --git a/frontend/stores/AuthStore.js b/frontend/stores/AuthStore.js
index dcc879c41..f5b77ecbe 100644
--- a/frontend/stores/AuthStore.js
+++ b/frontend/stores/AuthStore.js
@@ -2,7 +2,6 @@
import { observable, action, computed, autorunAsync } from 'mobx';
import invariant from 'invariant';
import { client } from 'utils/ApiClient';
-import UserStore from 'stores/UserStore';
import type { User, Team } from 'types';
const AUTH_STORE = 'AUTH_STORE';
@@ -72,17 +71,6 @@ class AuthStore {
};
};
- getUserStore(): UserStore {
- invariant(
- this.user && this.team,
- 'Tried to create a user store without data'
- );
- return new UserStore({
- user: this.user,
- team: this.team,
- });
- }
-
constructor() {
// Rehydrate
const data = JSON.parse(localStorage.getItem(AUTH_STORE) || '{}');
diff --git a/frontend/stores/DocumentsStore.js b/frontend/stores/DocumentsStore.js
index 3d839a1c9..444a47bbf 100644
--- a/frontend/stores/DocumentsStore.js
+++ b/frontend/stores/DocumentsStore.js
@@ -38,14 +38,17 @@ class DocumentsStore extends BaseStore {
/* Computed */
@computed get recentlyViewed(): Array {
- return _.filter(this.data.values(), ({ id }) =>
- this.recentlyViewedIds.includes(id)
+ return _.take(
+ _.filter(this.data.values(), ({ id }) =>
+ this.recentlyViewedIds.includes(id)
+ ),
+ 5
);
}
@computed get recentlyEdited(): Array {
// $FlowIssue
- return this.data.values();
+ return _.take(this.data.values(), 5);
}
@computed get starred(): Array {
@@ -60,11 +63,14 @@ class DocumentsStore extends BaseStore {
/* Actions */
- @action fetchAll = async (request: string = 'list'): Promise<*> => {
+ @action fetchAll = async (
+ request: string = 'list',
+ options: ?Object
+ ): Promise<*> => {
this.isFetching = true;
try {
- const res = await client.post(`/documents.${request}`);
+ const res = await client.post(`/documents.${request}`, options);
invariant(res && res.data, 'Document list not available');
const { data } = res;
runInAction('DocumentsStore#fetchAll', () => {
@@ -81,12 +87,17 @@ class DocumentsStore extends BaseStore {
}
};
- @action fetchRecentlyViewed = async (): Promise<*> => {
- const data = await this.fetchAll('viewed');
+ @action fetchRecentlyModified = async (options: ?Object): Promise<*> => {
+ return this.fetchAll('list', options);
+ };
+
+ @action fetchRecentlyViewed = async (options: ?Object): Promise<*> => {
+ const data = await this.fetchAll('viewed', options);
runInAction('DocumentsStore#fetchRecentlyViewed', () => {
this.recentlyViewedIds = _.map(data, 'id');
});
+ return data;
};
@action fetchStarred = async (): Promise<*> => {
diff --git a/frontend/stores/UserStore.js b/frontend/stores/UserStore.js
deleted file mode 100644
index 7d368c934..000000000
--- a/frontend/stores/UserStore.js
+++ /dev/null
@@ -1,32 +0,0 @@
-// @flow
-import { observable, computed } from 'mobx';
-import type { User, Team } from 'types';
-
-type Options = {
- user: User,
- team: Team,
-};
-
-class UserStore {
- @observable user: User;
- @observable team: Team;
-
- @observable isLoading: boolean = false;
-
- /* Computed */
-
- @computed get asJson(): string {
- return JSON.stringify({
- user: this.user,
- team: this.team,
- });
- }
-
- constructor(options: Options) {
- // Rehydrate
- this.user = options.user;
- this.team = options.team;
- }
-}
-
-export default UserStore;
diff --git a/frontend/styles/base.scss b/frontend/styles/base.scss
index 3fcc4acd2..1e745df7f 100644
--- a/frontend/styles/base.scss
+++ b/frontend/styles/base.scss
@@ -16,22 +16,18 @@
html, body, .viewport {
width: 100%;
+ min-height: 100vh;
margin: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
- font-size: 15px;
+ font-size: 16px;
line-height: 1.5;
margin: 0;
color: #617180;
background-color: #fff;
display: flex;
- position: absolute;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
diff --git a/frontend/styles/constants.js b/frontend/styles/constants.js
index 56bf05aa1..bfdf50e50 100644
--- a/frontend/styles/constants.js
+++ b/frontend/styles/constants.js
@@ -31,7 +31,7 @@ export const fontWeight = {
light: 300,
regular: 400,
medium: 500,
- demiBold: 600,
+ semiBold: 600,
bold: 700,
heavy: 800,
};
diff --git a/frontend/utils/ApiClient.js b/frontend/utils/ApiClient.js
index 5abf98ef9..aff280e95 100644
--- a/frontend/utils/ApiClient.js
+++ b/frontend/utils/ApiClient.js
@@ -89,11 +89,11 @@ class ApiClient {
});
};
- get = (path: string, data?: Object, options?: Object) => {
+ get = (path: string, data: ?Object, options?: Object) => {
return this.fetch(path, 'GET', data, options);
};
- post = (path: string, data?: Object, options?: Object) => {
+ post = (path: string, data: ?Object, options?: Object) => {
return this.fetch(path, 'POST', data, options);
};
diff --git a/package.json b/package.json
index 64b20ba46..b9ca985eb 100644
--- a/package.json
+++ b/package.json
@@ -146,6 +146,7 @@
"react-addons-css-transition-group": "15.3.2",
"react-dom": "^15.6.1",
"react-dropzone": "3.6.0",
+ "react-feather": "^1.0.7",
"react-helmet": "3.1.0",
"react-keydown": "^1.7.3",
"react-modal": "^2.2.1",
diff --git a/server/api/auth.js b/server/api/auth.js
index 811055cf5..35ae88c14 100644
--- a/server/api/auth.js
+++ b/server/api/auth.js
@@ -1,11 +1,9 @@
// @flow
import Router from 'koa-router';
-import apiError, { httpErrors } from '../errors';
-import fetch from 'isomorphic-fetch';
-import querystring from 'querystring';
-
+import apiError from '../errors';
import { presentUser, presentTeam } from '../presenters';
import { User, Team } from '../models';
+import * as Slack from '../slack';
const router = new Router();
@@ -88,24 +86,7 @@ router.post('auth.slack', async ctx => {
const { code } = ctx.body;
ctx.assertPresent(code, 'code is required');
- const body = {
- client_id: process.env.SLACK_KEY,
- client_secret: process.env.SLACK_SECRET,
- redirect_uri: `${process.env.URL || ''}/auth/slack`,
- code,
- };
-
- let data;
- try {
- const response = await fetch(
- `https://slack.com/api/oauth.access?${querystring.stringify(body)}`
- );
- data = await response.json();
- } catch (e) {
- throw httpErrors.BadRequest();
- }
-
- if (!data.ok) throw httpErrors.BadRequest(data.error);
+ const data = await Slack.oauthAccess(code);
// Temp to block
const allowedSlackDomains = (process.env.ALLOWED_SLACK_DOMAINS || '')
@@ -118,22 +99,20 @@ router.post('auth.slack', async ctx => {
);
}
- // User
let user = await User.findOne({ where: { slackId: data.user.id } });
-
- // Team
let team = await Team.findOne({ where: { slackId: data.team.id } });
const teamExisted = !!team;
- if (!team) {
+
+ if (team) {
+ team.name = data.team.name;
+ team.slackData = data.team;
+ await team.save();
+ } else {
team = await Team.create({
name: data.team.name,
slackId: data.team.id,
slackData: data.team,
});
- } else {
- team.name = data.team.name;
- team.slackData = data.team;
- team = await team.save();
}
if (user) {
@@ -143,7 +122,6 @@ router.post('auth.slack', async ctx => {
} else {
user = await User.create({
slackId: data.user.id,
- username: data.user.name,
name: data.user.name,
email: data.user.email,
teamId: team.id,
@@ -169,24 +147,7 @@ router.post('auth.slackCommands', async ctx => {
const { code } = ctx.body;
ctx.assertPresent(code, 'code is required');
- const body = {
- client_id: process.env.SLACK_KEY,
- client_secret: process.env.SLACK_SECRET,
- redirect_uri: `${process.env.URL || ''}/auth/slack/commands`,
- code,
- };
-
- let data;
- try {
- const response = await fetch(
- `https://slack.com/api/oauth.access?${querystring.stringify(body)}`
- );
- data = await response.json();
- } catch (e) {
- throw httpErrors.BadRequest();
- }
-
- if (!data.ok) throw httpErrors.BadRequest(data.error);
+ await Slack.oauthAccess(code, `${process.env.URL || ''}/auth/slack/commands`);
});
export default router;
diff --git a/server/api/middlewares/pagination.js b/server/api/middlewares/pagination.js
index 57ebf03a7..d0e94048b 100644
--- a/server/api/middlewares/pagination.js
+++ b/server/api/middlewares/pagination.js
@@ -10,8 +10,9 @@ export default function pagination(options) {
};
let query = ctx.request.query;
- let limit = parseInt(query.limit, 10);
- let offset = parseInt(query.offset, 10);
+ let body = ctx.request.body;
+ let limit = parseInt(query.limit || body.limit, 10);
+ let offset = parseInt(query.offset || body.offset, 10);
limit = isNaN(limit) ? opts.defaultLimit : limit;
offset = isNaN(offset) ? 0 : offset;
diff --git a/server/migrations/20170904202454-allow-null-username.js b/server/migrations/20170904202454-allow-null-username.js
new file mode 100644
index 000000000..399dc4e8c
--- /dev/null
+++ b/server/migrations/20170904202454-allow-null-username.js
@@ -0,0 +1,15 @@
+module.exports = {
+ up: async function(queryInterface, Sequelize) {
+ await queryInterface.changeColumn('users', 'username', {
+ type: Sequelize.STRING,
+ allowNull: true,
+ });
+ },
+
+ down: async function(queryInterface, Sequelize) {
+ await queryInterface.changeColumn('users', 'username', {
+ type: Sequelize.STRING,
+ allowNull: false,
+ });
+ },
+};
diff --git a/server/slack.js b/server/slack.js
new file mode 100644
index 000000000..62bb75eb5
--- /dev/null
+++ b/server/slack.js
@@ -0,0 +1,34 @@
+// @flow
+import fetch from 'isomorphic-fetch';
+import querystring from 'querystring';
+import { httpErrors } from './errors';
+
+const SLACK_API_URL = 'https://slack.com/api';
+
+export async function request(endpoint: string, body: Object) {
+ let data;
+ try {
+ const response = await fetch(
+ `${SLACK_API_URL}/${endpoint}?${querystring.stringify(body)}`
+ );
+ data = await response.json();
+ } catch (e) {
+ throw httpErrors.BadRequest();
+ }
+ console.log('DATA', data);
+ if (!data.ok) throw httpErrors.BadRequest(data.error);
+
+ return data;
+}
+
+export async function oauthAccess(
+ code: string,
+ redirect_uri: string = `${process.env.URL || ''}/auth/slack`
+) {
+ return request('oauth.access', {
+ client_id: process.env.SLACK_KEY,
+ client_secret: process.env.SLACK_SECRET,
+ redirect_uri: `${process.env.URL || ''}/auth/slack`,
+ code,
+ });
+}
diff --git a/server/static/dev.html b/server/static/dev.html
index 1933c80b3..8eea6eee8 100644
--- a/server/static/dev.html
+++ b/server/static/dev.html
@@ -20,7 +20,7 @@
#root {
flex: 1;
- height: 100%;
+ min-height: 100vh;
}
@@ -30,4 +30,4 @@