diff --git a/frontend/components/CenteredContent/CenteredContent.js b/frontend/components/CenteredContent/CenteredContent.js index cfc4fe08b..b9f15975a 100644 --- a/frontend/components/CenteredContent/CenteredContent.js +++ b/frontend/components/CenteredContent/CenteredContent.js @@ -12,7 +12,7 @@ const Container = styled.div` `; const Content = styled.div` - max-width: 740px; + max-width: 50em; margin: 0 auto; `; diff --git a/frontend/components/DropdownMenu/DropdownMenu.js b/frontend/components/DropdownMenu/DropdownMenu.js index 36c560041..69874c0ad 100644 --- a/frontend/components/DropdownMenu/DropdownMenu.js +++ b/frontend/components/DropdownMenu/DropdownMenu.js @@ -22,6 +22,7 @@ const DropdownMenuItem = ({ onClick, children }: MenuItemProps) => { type DropdownMenuProps = { label: React.Element, children?: React.Element, + style?: Object, }; @observer class DropdownMenu extends React.Component { @@ -42,7 +43,7 @@ type DropdownMenuProps = { {this.menuOpen && - + {this.props.children} } @@ -65,9 +66,7 @@ const Label = styled(Flex).attrs({ })` cursor: pointer; z-index: 1000; - min-height: 43px; - margin: 0 5px; `; const MenuContainer = styled.div` diff --git a/frontend/components/Editor/Editor.scss b/frontend/components/Editor/Editor.scss index a7694603d..7508c6021 100644 --- a/frontend/components/Editor/Editor.scss +++ b/frontend/components/Editor/Editor.scss @@ -1,9 +1,9 @@ .editor { font-weight: 400; font-size: 1em; - line-height: 1.5em; + line-height: 1.7em; width: 100%; - color: #1b2631; + color: #1b2830; h1, h2, diff --git a/frontend/components/Layout/Layout.js b/frontend/components/Layout/Layout.js index 9679eb833..c0a6121ee 100644 --- a/frontend/components/Layout/Layout.js +++ b/frontend/components/Layout/Layout.js @@ -10,11 +10,11 @@ import keydown from 'react-keydown'; import Flex from 'components/Flex'; import { color, layout } from 'styles/constants'; import { documentEditUrl, homeUrl, searchUrl } from 'utils/routeHelpers'; - import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu'; + +import Avatar from 'components/Avatar'; import { LoadingIndicatorBar } from 'components/LoadingIndicator'; import Scrollable from 'components/Scrollable'; -import Avatar from 'components/Avatar'; import Modal from 'components/Modal'; import AddIcon from 'components/Icon/AddIcon'; import MoreIcon from 'components/Icon/MoreIcon'; @@ -26,8 +26,8 @@ import Settings from 'scenes/Settings'; import SidebarCollection from './components/SidebarCollection'; import SidebarCollectionList from './components/SidebarCollectionList'; import SidebarLink from './components/SidebarLink'; +import HeaderBlock from './components/HeaderBlock'; -import UserStore from 'stores/UserStore'; import AuthStore from 'stores/AuthStore'; import UiStore from 'stores/UiStore'; import CollectionsStore from 'stores/CollectionsStore'; @@ -40,7 +40,6 @@ type Props = { children?: ?React.Element, actions?: ?React.Element, title?: ?React.Element, - user: UserStore, auth: AuthStore, ui: UiStore, search: ?boolean, @@ -108,7 +107,8 @@ type Props = { }; render() { - const { user, auth, documents, collections, history, ui } = this.props; + const { auth, documents, collections, history, ui } = this.props; + const { user, team } = auth; return ( @@ -129,34 +129,35 @@ type Props = { {auth.authenticated && user && + team && -
- - Atlas - - }> - - Settings - - - Keyboard shortcuts - - - API - - - Logout - - -
+ + + + } + > + + Settings + + + Keyboard shortcuts + + + API + + + Logout + + - - Search - Home + Search Starred @@ -228,7 +229,7 @@ type Props = { const CollectionAction = styled.a` position: absolute; - top: 8px; + top: -2px; right: ${layout.hpadding}; svg { @@ -250,46 +251,28 @@ const Container = styled(Flex)` height: 100%; `; -const LogoLink = styled(Link)` - margin-top: 15px; - font-family: 'Atlas Grotesk'; - font-weight: bold; - color: ${color.text}; - text-decoration: none; - font-size: 16px; +const Content = styled(Flex)` + margin-left: ${props => (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/HeaderBlock.js b/frontend/components/Layout/components/HeaderBlock.js new file mode 100644 index 000000000..8bf4fd57d --- /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/SidebarLink/SidebarLink.js b/frontend/components/Layout/components/SidebarLink/SidebarLink.js index 7df81e86c..d09f85c3f 100644 --- a/frontend/components/Layout/components/SidebarLink/SidebarLink.js +++ b/frontend/components/Layout/components/SidebarLink/SidebarLink.js @@ -17,6 +17,7 @@ const StyledNavLink = styled(NavLink)` display: block; padding: 5px ${layout.hpadding}; color: ${color.slateDark}; + font-size: 15px; &:hover { color: ${darken(0.1, color.slateDark)}; 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/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/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..cd822c1dc 100644 --- a/frontend/styles/base.scss +++ b/frontend/styles/base.scss @@ -21,17 +21,12 @@ html, body, .viewport { 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/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/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, + }); +}