diff --git a/app/components/Button/Button.js b/app/components/Button/Button.js
index 46700009..4a30c745 100644
--- a/app/components/Button/Button.js
+++ b/app/components/Button/Button.js
@@ -32,6 +32,11 @@ const RealButton = styled.button`
top: 0.05em;
}
+ &:disabled {
+ opacity: 0.8;
+ cursor: default;
+ }
+
${props =>
props.light &&
`
@@ -57,10 +62,7 @@ const RealButton = styled.button`
&:hover {
background: ${darken(0.05, color.danger)};
}
- `} &:disabled {
- background: ${color.slateLight};
- cursor: default;
- }
+ `};
`;
const Label = styled.span`
diff --git a/app/components/Icon/BackIcon.js b/app/components/Icon/BackIcon.js
index 4586fd1a..42f2c6a9 100644
--- a/app/components/Icon/BackIcon.js
+++ b/app/components/Icon/BackIcon.js
@@ -6,10 +6,7 @@ import type { Props } from './Icon';
export default function BackIcon(props: Props) {
return (
-
+
);
}
diff --git a/app/components/Icon/ProfileIcon.js b/app/components/Icon/ProfileIcon.js
new file mode 100644
index 00000000..7fc415b0
--- /dev/null
+++ b/app/components/Icon/ProfileIcon.js
@@ -0,0 +1,12 @@
+// @flow
+import React from 'react';
+import Icon from './Icon';
+import type { Props } from './Icon';
+
+export default function ProfileIcon(props: Props) {
+ return (
+
+
+
+ );
+}
diff --git a/app/components/Icon/SettingsIcon.js b/app/components/Icon/SettingsIcon.js
new file mode 100644
index 00000000..e00dca74
--- /dev/null
+++ b/app/components/Icon/SettingsIcon.js
@@ -0,0 +1,12 @@
+// @flow
+import React from 'react';
+import Icon from './Icon';
+import type { Props } from './Icon';
+
+export default function SettingsIcon(props: Props) {
+ return (
+
+
+
+ );
+}
diff --git a/app/components/Input/Input.js b/app/components/Input/Input.js
index 94c2d8ec..71608f1e 100644
--- a/app/components/Input/Input.js
+++ b/app/components/Input/Input.js
@@ -11,6 +11,7 @@ const RealTextarea = styled.textarea`
outline: none;
background: none;
+ &:disabled,
&::placeholder {
color: ${color.slate};
}
@@ -23,6 +24,7 @@ const RealInput = styled.input`
outline: none;
background: none;
+ &:disabled,
&::placeholder {
color: ${color.slate};
}
@@ -52,13 +54,18 @@ export const LabelText = styled.div`
`;
export type Props = {
- type: string,
+ type?: string,
value?: string,
label?: string,
className?: string,
};
-export default function Input({ type, label, className, ...rest }: Props) {
+export default function Input({
+ type = 'text',
+ label,
+ className,
+ ...rest
+}: Props) {
const InputComponent = type === 'textarea' ? RealTextarea : RealInput;
return (
diff --git a/app/components/Layout/Layout.js b/app/components/Layout/Layout.js
index 34e7a94b..a87ae84f 100644
--- a/app/components/Layout/Layout.js
+++ b/app/components/Layout/Layout.js
@@ -1,6 +1,6 @@
// @flow
import React from 'react';
-import { withRouter } from 'react-router-dom';
+import { Switch, Route, withRouter } from 'react-router-dom';
import type { Location } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import styled from 'styled-components';
@@ -13,6 +13,7 @@ import { documentEditUrl, homeUrl, searchUrl } from 'utils/routeHelpers';
import { LoadingIndicatorBar } from 'components/LoadingIndicator';
import Sidebar from 'components/Sidebar';
+import SettingsSidebar from 'components/Sidebar/Settings';
import Modals from 'components/Modals';
import Toasts from 'components/Toasts';
@@ -84,7 +85,12 @@ class Layout extends React.Component {
{this.props.notifications}
- {showSidebar && }
+ {showSidebar && (
+
+
+
+
+ )}
{this.props.children}
diff --git a/app/components/Modals/Modals.js b/app/components/Modals/Modals.js
index 00b4bb7c..b08ebf25 100644
--- a/app/components/Modals/Modals.js
+++ b/app/components/Modals/Modals.js
@@ -8,7 +8,6 @@ import CollectionEdit from 'scenes/CollectionEdit';
import CollectionDelete from 'scenes/CollectionDelete';
import DocumentDelete from 'scenes/DocumentDelete';
import KeyboardShortcuts from 'scenes/KeyboardShortcuts';
-import Settings from 'scenes/Settings';
@observer
class Modals extends Component {
@@ -52,9 +51,6 @@ class Modals extends Component {
-
-
-
);
}
diff --git a/app/components/Sidebar/Settings.js b/app/components/Sidebar/Settings.js
new file mode 100644
index 00000000..1d0767a6
--- /dev/null
+++ b/app/components/Sidebar/Settings.js
@@ -0,0 +1,90 @@
+// @flow
+import React, { Component } from 'react';
+import { withRouter } from 'react-router-dom';
+import type { Location } from 'react-router-dom';
+import styled from 'styled-components';
+import { observer, inject } from 'mobx-react';
+import Flex from 'shared/components/Flex';
+import { color, layout } from 'shared/styles/constants';
+
+import Scrollable from 'components/Scrollable';
+import ProfileIcon from 'components/Icon/ProfileIcon';
+import SettingsIcon from 'components/Icon/SettingsIcon';
+import CodeIcon from 'components/Icon/CodeIcon';
+import Header from './components/Header';
+import SidebarLink from './components/SidebarLink';
+import HeaderBlock from './components/HeaderBlock';
+import AuthStore from 'stores/AuthStore';
+
+type Props = {
+ history: Object,
+ location: Location,
+ auth: AuthStore,
+};
+
+@observer
+class Sidebar extends Component {
+ props: Props;
+
+ returnToDashboard = () => {
+ this.props.history.push('/');
+ };
+
+ render() {
+ const { team } = this.props.auth;
+ if (!team) return;
+
+ return (
+
+
+
+
+
+
+
+ }>
+ Profile
+
+ }>
+ API Tokens
+
+
+
+
+ }
+ >
+ Integrations
+
+
+
+
+
+ );
+ }
+}
+
+const Container = styled(Flex)`
+ position: fixed;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: ${layout.sidebarWidth};
+ background: ${color.smoke};
+ transition: left 200ms ease-in-out;
+`;
+
+const Section = styled(Flex)`
+ flex-direction: column;
+ margin: 24px 0;
+ padding: 0 24px;
+ position: relative;
+`;
+
+export default withRouter(inject('auth')(Sidebar));
diff --git a/app/components/Sidebar/Sidebar.js b/app/components/Sidebar/Sidebar.js
index 1242e663..92fbf89a 100644
--- a/app/components/Sidebar/Sidebar.js
+++ b/app/components/Sidebar/Sidebar.js
@@ -8,7 +8,6 @@ import Flex from 'shared/components/Flex';
import { color, layout } from 'shared/styles/constants';
import AccountMenu from 'menus/AccountMenu';
-import Avatar from 'components/Avatar';
import Scrollable from 'components/Scrollable';
import HomeIcon from 'components/Icon/HomeIcon';
import SearchIcon from 'components/Icon/SearchIcon';
@@ -63,9 +62,11 @@ class Sidebar extends Component {
-
-
+
}
/>
diff --git a/app/components/Sidebar/components/Collections.js b/app/components/Sidebar/components/Collections.js
index 157c410f..3fa4066f 100644
--- a/app/components/Sidebar/components/Collections.js
+++ b/app/components/Sidebar/components/Collections.js
@@ -5,8 +5,9 @@ import { observer, inject } from 'mobx-react';
import type { Location } from 'react-router-dom';
import Flex from 'shared/components/Flex';
import styled from 'styled-components';
-import { color, fontWeight } from 'shared/styles/constants';
+import { color } from 'shared/styles/constants';
+import Header from './Header';
import SidebarLink from './SidebarLink';
import DropToImport from 'components/DropToImport';
import PlusIcon from 'components/Icon/PlusIcon';
@@ -261,15 +262,6 @@ const StyledDropToImport = styled(DropToImport)`
}
`;
-const Header = styled(Flex)`
- font-size: 12px;
- font-weight: ${fontWeight.semiBold};
- text-transform: uppercase;
- color: ${color.slate};
- letter-spacing: 0.04em;
- margin-bottom: 4px;
-`;
-
const Children = styled(Flex)`
margin-left: 12px;
`;
diff --git a/app/components/Sidebar/components/Header.js b/app/components/Sidebar/components/Header.js
new file mode 100644
index 00000000..63cff780
--- /dev/null
+++ b/app/components/Sidebar/components/Header.js
@@ -0,0 +1,15 @@
+// @flow
+import Flex from 'shared/components/Flex';
+import styled from 'styled-components';
+import { color, fontWeight } from 'shared/styles/constants';
+
+const Header = styled(Flex)`
+ font-size: 11px;
+ font-weight: ${fontWeight.semiBold};
+ text-transform: uppercase;
+ color: ${color.slateDark};
+ letter-spacing: 0.04em;
+ margin-bottom: 4px;
+`;
+
+export default Header;
diff --git a/app/components/Sidebar/components/HeaderBlock.js b/app/components/Sidebar/components/HeaderBlock.js
index 242a4442..be93d154 100644
--- a/app/components/Sidebar/components/HeaderBlock.js
+++ b/app/components/Sidebar/components/HeaderBlock.js
@@ -2,33 +2,38 @@
import React from 'react';
import styled from 'styled-components';
import { color } from 'shared/styles/constants';
-import type { User, Team } from 'types';
import Flex from 'shared/components/Flex';
+import TeamLogo from './TeamLogo';
type Props = {
- user: User,
- team: Team,
- children?: React$Element,
+ teamName: string,
+ subheading: string,
+ logoUrl: string,
};
-function HeaderBlock({ user, team, children }: Props) {
+function HeaderBlock({ teamName, subheading, logoUrl, ...rest }: Props) {
return (
-
+
+
- {team.name}
- {user.name}
+ {teamName}
+ {subheading}
- {children}
);
}
-const UserName = styled.div`
- font-size: 13px;
+const Subheading = styled.div`
+ padding-left: 10px;
+ font-size: 11px;
+ text-transform: uppercase;
+ font-weight: 500;
+ color: ${color.slateDark};
`;
const TeamName = styled.div`
- font-weight: bold;
+ padding-left: 10px;
+ font-weight: 600;
color: ${color.text};
text-decoration: none;
font-size: 16px;
@@ -43,18 +48,9 @@ const Header = styled(Flex)`
&:active,
&:hover {
+ transition: background 100ms ease-in-out;
background: rgba(0, 0, 0, 0.05);
}
-
- &::after {
- content: '';
- left: 24px;
- right: 24px;
- background: rgba(0, 0, 0, 0.075);
- height: 1px;
- position: absolute;
- bottom: 0;
- }
`;
export default HeaderBlock;
diff --git a/app/components/Sidebar/components/TeamLogo.js b/app/components/Sidebar/components/TeamLogo.js
new file mode 100644
index 00000000..f699b32a
--- /dev/null
+++ b/app/components/Sidebar/components/TeamLogo.js
@@ -0,0 +1,13 @@
+// @flow
+import styled from 'styled-components';
+import { color } from 'shared/styles/constants';
+
+const TeamLogo = styled.img`
+ width: 38px;
+ height: 38px;
+ border-radius: 4px;
+ background: ${color.white};
+ border: 1px solid ${color.slateLight};
+`;
+
+export default TeamLogo;
diff --git a/app/components/SlackAuthLink/SlackAuthLink.js b/app/components/SlackAuthLink/SlackAuthLink.js
deleted file mode 100644
index d4f8c164..00000000
--- a/app/components/SlackAuthLink/SlackAuthLink.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// @flow
-import React from 'react';
-import { inject } from 'mobx-react';
-import { slackAuth } from 'shared/utils/routeHelpers';
-import AuthStore from 'stores/AuthStore';
-
-type Props = {
- children: React$Element<*>,
- auth: AuthStore,
- scopes?: string[],
- redirectUri?: string,
-};
-
-function SlackAuthLink({ auth, children, scopes, redirectUri }: Props) {
- return (
-
- {children}
-
- );
-}
-
-export default inject('auth')(SlackAuthLink);
diff --git a/app/components/SlackAuthLink/assets/slack_icon.svg b/app/components/SlackAuthLink/assets/slack_icon.svg
deleted file mode 100644
index fc10280f..00000000
--- a/app/components/SlackAuthLink/assets/slack_icon.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
\ No newline at end of file
diff --git a/app/components/SlackAuthLink/index.js b/app/components/SlackAuthLink/index.js
deleted file mode 100644
index 5984174e..00000000
--- a/app/components/SlackAuthLink/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-// @flow
-import SlackAuthLink from './SlackAuthLink';
-export default SlackAuthLink;
diff --git a/app/components/Subheading.js b/app/components/Subheading.js
new file mode 100644
index 00000000..32cbe5be
--- /dev/null
+++ b/app/components/Subheading.js
@@ -0,0 +1,17 @@
+// @flow
+import styled from 'styled-components';
+import { color } from 'shared/styles/constants';
+
+const Subheading = styled.h3`
+ font-size: 11px;
+ font-weight: 500;
+ text-transform: uppercase;
+ color: ${color.slate};
+ letter-spacing: 0.04em;
+ border-bottom: 1px solid ${color.slateLight};
+ padding-bottom: 8px;
+ margin-top: 30px;
+ margin-bottom: 10px;
+`;
+
+export default Subheading;
diff --git a/app/index.js b/app/index.js
index 8341b0c4..4b1a1084 100644
--- a/app/index.js
+++ b/app/index.js
@@ -10,6 +10,7 @@ import {
} from 'react-router-dom';
import stores from 'stores';
+import SettingsStore from 'stores/SettingsStore';
import DocumentsStore from 'stores/DocumentsStore';
import CollectionsStore from 'stores/CollectionsStore';
import CacheStore from 'stores/CacheStore';
@@ -22,6 +23,9 @@ import Starred from 'scenes/Starred';
import Collection from 'scenes/Collection';
import Document from 'scenes/Document';
import Search from 'scenes/Search';
+import Settings from 'scenes/Settings';
+import Slack from 'scenes/Settings/Slack';
+import Tokens from 'scenes/Settings/Tokens';
import SlackAuth from 'scenes/SlackAuth';
import ErrorAuth from 'scenes/ErrorAuth';
import Error404 from 'scenes/Error404';
@@ -54,6 +58,7 @@ const Auth = ({ children }: AuthProps) => {
const { user, team } = stores.auth;
const cache = new CacheStore(user.id);
authenticatedStores = {
+ settings: new SettingsStore(),
documents: new DocumentsStore({
ui: stores.ui,
cache,
@@ -110,6 +115,14 @@ render(
+
+
+
+
{
- this.props.ui.setActiveModal('settings');
+ this.props.history.push('/settings');
};
handleApi = () => {
diff --git a/app/scenes/Dashboard/Dashboard.js b/app/scenes/Dashboard/Dashboard.js
index 19a195c9..b33042fc 100644
--- a/app/scenes/Dashboard/Dashboard.js
+++ b/app/scenes/Dashboard/Dashboard.js
@@ -2,26 +2,15 @@
import React, { Component } from 'react';
import { observable } from 'mobx';
import { observer, inject } from 'mobx-react';
-import styled from 'styled-components';
import DocumentsStore from 'stores/DocumentsStore';
import Flex from 'shared/components/Flex';
import DocumentList from 'components/DocumentList';
import PageTitle from 'components/PageTitle';
+import Subheading from 'components/Subheading';
import CenteredContent from 'components/CenteredContent';
import { ListPlaceholder } from 'components/LoadingPlaceholder';
-const Subheading = styled.h3`
- font-size: 11px;
- font-weight: 500;
- text-transform: uppercase;
- color: #9fa6ab;
- letter-spacing: 0.04em;
- border-bottom: 1px solid #ddd;
- padding-bottom: 10px;
- margin-top: 30px;
-`;
-
type Props = {
documents: DocumentsStore,
};
diff --git a/app/scenes/Settings/Settings.js b/app/scenes/Settings/Settings.js
index 6650bc65..6172b1b4 100644
--- a/app/scenes/Settings/Settings.js
+++ b/app/scenes/Settings/Settings.js
@@ -1,179 +1,43 @@
// @flow
import React, { Component } from 'react';
-import { observable } from 'mobx';
-import { observer } from 'mobx-react';
-import { Link } from 'react-router-dom';
-import styled from 'styled-components';
-import ApiKeyRow from './components/ApiKeyRow';
-import SettingsStore from './SettingsStore';
-import { color } from 'shared/styles/constants';
+import { observer, inject } from 'mobx-react';
-import Flex from 'shared/components/Flex';
-import Button from 'components/Button';
+import AuthStore from 'stores/AuthStore';
import Input from 'components/Input';
+import CenteredContent from 'components/CenteredContent';
+import PageTitle from 'components/PageTitle';
import HelpText from 'components/HelpText';
-import { Label } from 'components/Labeled';
-import SlackAuthLink from 'components/SlackAuthLink';
@observer
class Settings extends Component {
- store: SettingsStore;
-
- constructor() {
- super();
- this.store = new SettingsStore();
- }
-
- render() {
- const showSlackSettings = DEPLOYMENT === 'hosted';
-
- return (
-
- {showSlackSettings && (
-
- Slack
-
- Connect Outline to your Slack to instantly search for your
- documents using /outline
command.
-
-
-
-
-
-
- )}
-
-
- API Access
-
- Create API tokens to hack on your Outline. Learn more in{' '}
- API documentation.
-
-
- {this.store.apiKeys && (
-
-
- {this.store.apiKeys &&
- this.store.apiKeys.map(key => (
-
- ))}
-
-
- )}
-
-
-
- );
- }
-}
-
-@observer
-class InlineForm extends Component {
props: {
- placeholder: string,
- buttonLabel: string,
- name: string,
- value: ?string,
- onChange: Function,
- onSubmit: Function,
- disabled?: ?boolean,
- };
-
- @observable validationError: boolean = false;
- validationTimeout: number;
-
- componentWillUnmount() {
- clearTimeout(this.validationTimeout);
- }
-
- handleSubmit = event => {
- event.preventDefault();
- if (this.props.value) {
- this.props.onSubmit();
- } else {
- this.validationError = true;
- this.validationTimeout = setTimeout(
- () => (this.validationError = false),
- 2500
- );
- }
+ auth: AuthStore,
};
render() {
- const { placeholder, value, onChange, buttonLabel } = this.props;
+ const { user } = this.props.auth;
+ if (!user) return null;
return (
-
+
+
+ Profile
+
+ You’re signed in to Outline with Slack. To update your profile
+ information here please{' '}
+
+ update your profile on Slack
+ {' '}
+ and re-login to refresh.
+
+
+
+
);
}
}
-const Section = styled.div`
- margin-bottom: 40px;
-`;
-
-const Table = styled.table`
- margin-bottom: 20px;
- width: 100%;
-
- td {
- margin-right: 20px;
- color: ${color.slate};
- }
-`;
-
-const SectionLabel = styled(Label)`
- padding-bottom: 12px;
- margin-bottom: 20px;
- border-bottom: 1px solid #eaebea;
-`;
-
-const Code = styled.code`
- padding: 4px 6px;
- margin: 0 2px;
- background: #eaebea;
- border-radius: 4px;
-`;
-
-const ApiKeyInput = styled(Input)`
- width: 100%;
- margin-right: 12px;
-`;
-
-export default Settings;
+export default inject('auth')(Settings);
diff --git a/app/scenes/Settings/Slack.js b/app/scenes/Settings/Slack.js
new file mode 100644
index 00000000..3af70f4f
--- /dev/null
+++ b/app/scenes/Settings/Slack.js
@@ -0,0 +1,39 @@
+// @flow
+import React, { Component } from 'react';
+import { observer } from 'mobx-react';
+import styled from 'styled-components';
+
+import CenteredContent from 'components/CenteredContent';
+import PageTitle from 'components/PageTitle';
+import HelpText from 'components/HelpText';
+import SlackButton from './components/SlackButton';
+
+@observer
+class Slack extends Component {
+ render() {
+ return (
+
+
+ Slack
+
+ Connect Outline to your Slack team to instantly search for documents
+ using the /outline
command.
+
+
+
+
+ );
+ }
+}
+
+const Code = styled.code`
+ padding: 4px 6px;
+ margin: 0 2px;
+ background: #eaebea;
+ border-radius: 4px;
+`;
+
+export default Slack;
diff --git a/app/scenes/Settings/Tokens.js b/app/scenes/Settings/Tokens.js
new file mode 100644
index 00000000..169ef5d3
--- /dev/null
+++ b/app/scenes/Settings/Tokens.js
@@ -0,0 +1,99 @@
+// @flow
+import React, { Component } from 'react';
+import { observable } from 'mobx';
+import { observer, inject } from 'mobx-react';
+import { Link } from 'react-router-dom';
+import styled from 'styled-components';
+import ApiToken from './components/ApiToken';
+import SettingsStore from 'stores/SettingsStore';
+import { color } from 'shared/styles/constants';
+
+import Button from 'components/Button';
+import Input from 'components/Input';
+import CenteredContent from 'components/CenteredContent';
+import PageTitle from 'components/PageTitle';
+import HelpText from 'components/HelpText';
+import Subheading from 'components/Subheading';
+
+@observer
+class Settings extends Component {
+ @observable name: string = '';
+ props: {
+ settings: SettingsStore,
+ };
+
+ componentDidMount() {
+ this.props.settings.fetchApiKeys();
+ }
+
+ handleUpdate = (ev: SyntheticInputEvent) => {
+ this.name = ev.target.value;
+ };
+
+ handleSubmit = async (ev: SyntheticEvent) => {
+ ev.preventDefault();
+ await this.props.settings.createApiKey(this.name);
+ this.name = '';
+ };
+
+ render() {
+ const { settings } = this.props;
+ const hasApiKeys = settings.apiKeys.length > 0;
+
+ return (
+
+
+ API Tokens
+
+ {hasApiKeys && [
+ Your tokens,
+
+
+ {settings.apiKeys.map(key => (
+
+ ))}
+
+
,
+ Create a token,
+ ]}
+
+
+ You can create unlimited personal API tokens to hack on your wiki.
+ Learn more in the API documentation.
+
+
+
+
+ );
+ }
+}
+
+const Table = styled.table`
+ margin-bottom: 30px;
+ width: 100%;
+
+ td {
+ margin-right: 20px;
+ color: ${color.slate};
+ }
+`;
+
+export default inject('settings')(Settings);
diff --git a/app/scenes/Settings/components/ApiKeyRow.js b/app/scenes/Settings/components/ApiToken.js
similarity index 56%
rename from app/scenes/Settings/components/ApiKeyRow.js
rename to app/scenes/Settings/components/ApiToken.js
index ace9d210..649489e1 100644
--- a/app/scenes/Settings/components/ApiKeyRow.js
+++ b/app/scenes/Settings/components/ApiToken.js
@@ -2,18 +2,17 @@
import React from 'react';
import { observable } from 'mobx';
import { observer } from 'mobx-react';
-import styled from 'styled-components';
-import { color } from 'shared/styles/constants';
+import Button from 'components/Button';
type Props = {
id: string,
name: ?string,
secret: string,
- onDelete: Function,
+ onDelete: (id: string) => *,
};
@observer
-class ApiKeyRow extends React.Component {
+class ApiToken extends React.Component {
props: Props;
@observable disabled: boolean;
@@ -32,21 +31,14 @@ class ApiKeyRow extends React.Component {
{secret}
|
-
-
- Action
-
+ |
+
|
);
}
}
-const Action = styled.span`
- font-size: 14px;
- color: ${color.text};
-
- opacity: ${({ disabled }) => (disabled ? 0.5 : 1)};
-`;
-
-export default ApiKeyRow;
+export default ApiToken;
diff --git a/app/scenes/Settings/components/SlackButton.js b/app/scenes/Settings/components/SlackButton.js
new file mode 100644
index 00000000..9dcb63c5
--- /dev/null
+++ b/app/scenes/Settings/components/SlackButton.js
@@ -0,0 +1,35 @@
+// @flow
+import React from 'react';
+import styled from 'styled-components';
+import { inject } from 'mobx-react';
+import { slackAuth } from 'shared/utils/routeHelpers';
+import Button from 'components/Button';
+import SlackLogo from 'shared/components/SlackLogo';
+import AuthStore from 'stores/AuthStore';
+
+type Props = {
+ auth: AuthStore,
+ scopes?: string[],
+ redirectUri?: string,
+};
+
+function SlackButton({ auth, scopes, redirectUri }: Props) {
+ const handleClick = () =>
+ (window.location.href = slackAuth(
+ auth.getOauthState(),
+ scopes,
+ redirectUri
+ ));
+
+ return (
+ } neutral>
+ Add to Slack
+
+ );
+}
+
+const SpacedSlackLogo = styled(SlackLogo)`
+ padding-right: 4px;
+`;
+
+export default inject('auth')(SlackButton);
diff --git a/app/scenes/Settings/SettingsStore.js b/app/stores/SettingsStore.js
similarity index 65%
rename from app/scenes/Settings/SettingsStore.js
rename to app/stores/SettingsStore.js
index 3e5a3c36..a59105e0 100644
--- a/app/scenes/Settings/SettingsStore.js
+++ b/app/stores/SettingsStore.js
@@ -4,11 +4,10 @@ import invariant from 'invariant';
import { client } from 'utils/ApiClient';
import type { ApiKey } from 'types';
-class SearchStore {
+class SettingsStore {
@observable apiKeys: ApiKey[] = [];
- @observable keyName: ?string;
-
@observable isFetching: boolean = false;
+ @observable isSaving: boolean = false;
@action
fetchApiKeys = async () => {
@@ -29,50 +28,33 @@ class SearchStore {
};
@action
- createApiKey = async () => {
- this.isFetching = true;
+ createApiKey = async (name: string) => {
+ this.isSaving = true;
try {
- const res = await client.post('/apiKeys.create', {
- name: this.keyName ? this.keyName : 'Untitled key',
- });
+ const res = await client.post('/apiKeys.create', { name });
invariant(res && res.data, 'Data should be available');
const { data } = res;
runInAction('createApiKey', () => {
this.apiKeys.push(data);
- this.keyName = '';
});
} catch (e) {
console.error('Something went wrong');
}
- this.isFetching = false;
+ this.isSaving = false;
};
@action
deleteApiKey = async (id: string) => {
- this.isFetching = true;
-
try {
- await client.post('/apiKeys.delete', {
- id,
- });
+ await client.post('/apiKeys.delete', { id });
runInAction('deleteApiKey', () => {
this.fetchApiKeys();
});
} catch (e) {
console.error('Something went wrong');
}
- this.isFetching = false;
};
-
- @action
- setKeyName = (value: SyntheticInputEvent) => {
- this.keyName = value.target.value;
- };
-
- constructor() {
- this.fetchApiKeys();
- }
}
-export default SearchStore;
+export default SettingsStore;
diff --git a/app/types/index.js b/app/types/index.js
index a0d1688b..63d86139 100644
--- a/app/types/index.js
+++ b/app/types/index.js
@@ -3,12 +3,14 @@ export type User = {
avatarUrl: string,
id: string,
name: string,
+ email: string,
username: string,
};
export type Team = {
id: string,
name: string,
+ avatarUrl: string,
};
export type NavigationNode = {
diff --git a/server/api/__snapshots__/user.test.js.snap b/server/api/__snapshots__/user.test.js.snap
index 2f6503ac..888d2362 100644
--- a/server/api/__snapshots__/user.test.js.snap
+++ b/server/api/__snapshots__/user.test.js.snap
@@ -13,6 +13,7 @@ exports[`#user.info should return known user 1`] = `
Object {
"data": Object {
"avatarUrl": "http://example.com/avatar.png",
+ "email": "user1@example.com",
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
"name": "User 1",
"username": "user1",
diff --git a/server/presenters/__snapshots__/user.test.js.snap b/server/presenters/__snapshots__/user.test.js.snap
index a4e6bd70..8a5c7ad1 100644
--- a/server/presenters/__snapshots__/user.test.js.snap
+++ b/server/presenters/__snapshots__/user.test.js.snap
@@ -3,6 +3,7 @@
exports[`presents a user 1`] = `
Object {
"avatarUrl": "http://example.com/avatar.png",
+ "email": undefined,
"id": "123",
"name": "Test User",
"username": "testuser",
@@ -12,6 +13,7 @@ Object {
exports[`presents a user without slack data 1`] = `
Object {
"avatarUrl": null,
+ "email": undefined,
"id": "123",
"name": "Test User",
"username": "testuser",
diff --git a/server/presenters/team.js b/server/presenters/team.js
index 6587798a..d4121b68 100644
--- a/server/presenters/team.js
+++ b/server/presenters/team.js
@@ -7,6 +7,8 @@ function present(ctx: Object, team: Team) {
return {
id: team.id,
name: team.name,
+ avatarUrl:
+ team.avatarUrl || (team.slackData ? team.slackData.image_88 : null),
};
}
diff --git a/server/presenters/user.js b/server/presenters/user.js
index 4e9a7e3d..1281c45d 100644
--- a/server/presenters/user.js
+++ b/server/presenters/user.js
@@ -8,6 +8,7 @@ function present(ctx: Object, user: User) {
id: user.id,
username: user.username,
name: user.name,
+ email: user.email,
avatarUrl:
user.avatarUrl || (user.slackData ? user.slackData.image_192 : null),
};
diff --git a/shared/components/SlackLogo.js b/shared/components/SlackLogo.js
index a4512f2e..ea644941 100644
--- a/shared/components/SlackLogo.js
+++ b/shared/components/SlackLogo.js
@@ -1,14 +1,21 @@
// @flow
import React from 'react';
-function SlackLogo() {
+type Props = {
+ size?: number,
+ fill?: string,
+ className?: string,
+};
+
+function SlackLogo({ size = 34, fill = '#FFF', className }: Props) {
return (