Initial work on the frontend

This commit is contained in:
Jori Lallo
2018-03-04 15:39:17 -08:00
parent 3d6b9466fb
commit 06a6573feb
6 changed files with 142 additions and 45 deletions

View File

@ -2,7 +2,8 @@
import React from 'react'; import React from 'react';
import { Provider } from 'mobx-react'; import { Provider } from 'mobx-react';
import stores from 'stores'; import stores from 'stores';
import SettingsStore from 'stores/SettingsStore'; import ApiKeysStore from 'stores/settings/ApiKeysStore';
import MembersStore from 'stores/settings/MembersStore';
import DocumentsStore from 'stores/DocumentsStore'; import DocumentsStore from 'stores/DocumentsStore';
import CollectionsStore from 'stores/CollectionsStore'; import CollectionsStore from 'stores/CollectionsStore';
import CacheStore from 'stores/CacheStore'; import CacheStore from 'stores/CacheStore';
@ -22,7 +23,8 @@ const Auth = ({ children }: Props) => {
const { user, team } = stores.auth; const { user, team } = stores.auth;
const cache = new CacheStore(user.id); const cache = new CacheStore(user.id);
authenticatedStores = { authenticatedStores = {
settings: new SettingsStore(), apiKeys: new ApiKeysStore(),
members: new MembersStore(),
documents: new DocumentsStore({ documents: new DocumentsStore({
ui: stores.ui, ui: stores.ui,
cache, cache,

View File

@ -1,5 +1,6 @@
// @flow // @flow
import React, { Component } from 'react'; import React, { Component } from 'react';
import invariant from 'invariant';
import { observable } from 'mobx'; import { observable } from 'mobx';
import { observer, inject } from 'mobx-react'; import { observer, inject } from 'mobx-react';
import styled from 'styled-components'; import styled from 'styled-components';
@ -7,17 +8,20 @@ import Flex from 'shared/components/Flex';
import Avatar from 'components/Avatar'; import Avatar from 'components/Avatar';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
import AuthStore from 'stores/AuthStore';
import ErrorsStore from 'stores/ErrorsStore'; import ErrorsStore from 'stores/ErrorsStore';
import SettingsStore from 'stores/SettingsStore'; import MembersStore from 'stores/settings/MembersStore';
import CenteredContent from 'components/CenteredContent'; import CenteredContent from 'components/CenteredContent';
import LoadingPlaceholder from 'components/LoadingPlaceholder'; import LoadingPlaceholder from 'components/LoadingPlaceholder';
import PageTitle from 'components/PageTitle'; import PageTitle from 'components/PageTitle';
import MemberMenu from './components/MemberMenu';
@observer @observer
class Members extends Component { class Members extends Component {
props: { props: {
auth: AuthStore,
errors: ErrorsStore, errors: ErrorsStore,
settings: SettingsStore, members: MembersStore,
}; };
@observable members; @observable members;
@ -27,28 +31,37 @@ class Members extends Component {
@observable isInviting: boolean = false; @observable isInviting: boolean = false;
componentDidMount() { componentDidMount() {
this.props.settings.fetchMembers(); this.props.members.fetchMembers();
} }
render() { render() {
const user = this.props.auth.user;
invariant(user, 'User should exist');
return ( return (
<CenteredContent> <CenteredContent>
<PageTitle title="Members" /> <PageTitle title="Members" />
<h1>Members</h1> <h1>Members</h1>
{!this.props.settings.isFetching ? ( {!this.props.members.isFetching ? (
<Flex column> <Flex column>
{this.props.settings.members && ( {this.props.members.members && (
<MemberList column> <MemberList column>
{this.props.settings.members.map(member => ( {this.props.members.members.map(member => (
<Member key={member.id} justify="space-between" auto> <Member key={member.id} justify="space-between" auto>
<Flex> <Flex>
<Avatar src={member.avatarUrl} /> <Avatar src={member.avatarUrl} />
<UserName> <UserName>
{member.name} {member.email && `(${member.email})`} {member.name} {member.email && `(${member.email})`}
{member.isAdmin && <AdminBadge>Admin</AdminBadge>} {member.isAdmin && (
<Badge admin={member.isAdmin}>Admin</Badge>
)}
{member.isSuspended && <Badge>Suspended</Badge>}
</UserName> </UserName>
</Flex> </Flex>
<Flex>
{user.id !== member.id && <MemberMenu user={member} />}
</Flex>
</Member> </Member>
))} ))}
</MemberList> </MemberList>
@ -84,12 +97,15 @@ const UserName = styled.span`
padding-left: 8px; padding-left: 8px;
`; `;
const AdminBadge = styled.span` const Badge = styled.span`
margin-left: 10px; margin-left: 10px;
color: #777; padding: 2px 6px;
font-size: 13px; background-color: ${({ admin }) => (admin ? color.primary : color.smokeDark)};
color: ${({ admin }) => (admin ? color.white : color.text)};
border-radius: 4px;
font-size: 11px;
text-transform: uppercase; text-transform: uppercase;
font-weight: normal; font-weight: normal;
`; `;
export default inject('errors', 'settings')(Members); export default inject('auth', 'errors', 'members')(Members);

View File

@ -5,7 +5,7 @@ import { observer, inject } from 'mobx-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import styled from 'styled-components'; import styled from 'styled-components';
import ApiToken from './components/ApiToken'; import ApiToken from './components/ApiToken';
import SettingsStore from 'stores/SettingsStore'; import ApiKeysStore from 'stores/settings/ApiKeysStore';
import { color } from 'shared/styles/constants'; import { color } from 'shared/styles/constants';
import Button from 'components/Button'; import Button from 'components/Button';
@ -19,11 +19,11 @@ import Subheading from 'components/Subheading';
class Tokens extends Component { class Tokens extends Component {
@observable name: string = ''; @observable name: string = '';
props: { props: {
settings: SettingsStore, apiKeys: ApiKeysStore,
}; };
componentDidMount() { componentDidMount() {
this.props.settings.fetchApiKeys(); this.props.apiKeys.fetchApiKeys();
} }
handleUpdate = (ev: SyntheticInputEvent) => { handleUpdate = (ev: SyntheticInputEvent) => {
@ -32,13 +32,13 @@ class Tokens extends Component {
handleSubmit = async (ev: SyntheticEvent) => { handleSubmit = async (ev: SyntheticEvent) => {
ev.preventDefault(); ev.preventDefault();
await this.props.settings.createApiKey(this.name); await this.props.apiKeys.createApiKey(this.name);
this.name = ''; this.name = '';
}; };
render() { render() {
const { settings } = this.props; const { apiKeys } = this.props;
const hasApiKeys = settings.apiKeys.length > 0; const hasApiKeys = apiKeys.apiKeys.length > 0;
return ( return (
<CenteredContent> <CenteredContent>
@ -49,13 +49,13 @@ class Tokens extends Component {
<Subheading>Your tokens</Subheading>, <Subheading>Your tokens</Subheading>,
<Table> <Table>
<tbody> <tbody>
{settings.apiKeys.map(key => ( {apiKeys.apiKeys.map(key => (
<ApiToken <ApiToken
id={key.id} id={key.id}
key={key.id} key={key.id}
name={key.name} name={key.name}
secret={key.secret} secret={key.secret}
onDelete={settings.deleteApiKey} onDelete={apiKeys.deleteApiKey}
/> />
))} ))}
</tbody> </tbody>
@ -78,7 +78,7 @@ class Tokens extends Component {
<Button <Button
type="submit" type="submit"
value="Create Token" value="Create Token"
disabled={settings.isSaving} disabled={apiKeys.isSaving}
/> />
</form> </form>
</CenteredContent> </CenteredContent>
@ -96,4 +96,4 @@ const Table = styled.table`
} }
`; `;
export default inject('settings')(Tokens); export default inject('apiKeys')(Tokens);

View File

@ -0,0 +1,67 @@
// @flow
import React, { Component } from 'react';
import { inject, observer } from 'mobx-react';
import styled from 'styled-components';
import MembersStore from 'stores/settings/MembersStore';
import MoreIcon from 'components/Icon/MoreIcon';
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
import type { User } from 'types';
type Props = {
user: User,
members: MembersStore,
};
@observer
class MemberMenu extends Component {
props: Props;
handlePromote = (ev: SyntheticEvent) => {
ev.preventDefault();
};
handleDemote = (ev: SyntheticEvent) => {
ev.preventDefault();
};
handleSuspend = (ev: SyntheticEvent) => {
ev.preventDefault();
};
handleActivate = (ev: SyntheticEvent) => {
ev.preventDefault();
};
render() {
const { user } = this.props;
return (
<span>
<DropdownMenu label={<MoreIcon />}>
{!user.isSuspended &&
(user.isAdmin ? (
<DropdownMenuItem onClick={this.handleDemote}>
Make {user.name} a member
</DropdownMenuItem>
) : (
<DropdownMenuItem onClick={this.handlePromote}>
Make {user.name} an admin
</DropdownMenuItem>
))}
{user.isSuspended ? (
<DropdownMenuItem onClick={this.handleActivate}>
Activate account
</DropdownMenuItem>
) : (
<DropdownMenuItem onClick={this.handleSuspend}>
Suspend account
</DropdownMenuItem>
)}
</DropdownMenu>
</span>
);
}
}
export default inject('members')(MemberMenu);

View File

@ -2,11 +2,10 @@
import { observable, action, runInAction } from 'mobx'; import { observable, action, runInAction } from 'mobx';
import invariant from 'invariant'; import invariant from 'invariant';
import { client } from 'utils/ApiClient'; import { client } from 'utils/ApiClient';
import type { ApiKey, User } from 'types'; import type { ApiKey } from 'types';
class SettingsStore { class SettingsApiKeysStore {
@observable apiKeys: ApiKey[] = []; @observable apiKeys: ApiKey[] = [];
@observable members: User[] = [];
@observable isFetching: boolean = false; @observable isFetching: boolean = false;
@observable isSaving: boolean = false; @observable isSaving: boolean = false;
@ -56,24 +55,6 @@ class SettingsStore {
console.error('Something went wrong'); console.error('Something went wrong');
} }
}; };
@action
fetchMembers = async () => {
this.isFetching = true;
try {
const res = await client.post('/team.users');
invariant(res && res.data, 'Data should be available');
const { data } = res;
runInAction('fetchMembers', () => {
this.members = data.reverse();
});
} catch (e) {
console.error('Something went wrong');
}
this.isFetching = false;
};
} }
export default SettingsStore; export default SettingsApiKeysStore;

View File

@ -0,0 +1,31 @@
// @flow
import { observable, action, runInAction } from 'mobx';
import invariant from 'invariant';
import { client } from 'utils/ApiClient';
import type { User } from 'types';
class SettingsUsersStore {
@observable members: User[] = [];
@observable isFetching: boolean = false;
@observable isSaving: boolean = false;
@action
fetchMembers = async () => {
this.isFetching = true;
try {
const res = await client.post('/team.users');
invariant(res && res.data, 'Data should be available');
const { data } = res;
runInAction('fetchMembers', () => {
this.members = data.reverse();
});
} catch (e) {
console.error('Something went wrong');
}
this.isFetching = false;
};
}
export default SettingsUsersStore;