@ -49,7 +49,7 @@ const Modal = ({
|
||||
isOpen={isOpen}
|
||||
{...rest}
|
||||
>
|
||||
<Content column>
|
||||
<Content onClick={ev => ev.stopPropagation()} column>
|
||||
{title && <h1>{title}</h1>}
|
||||
<Close onClick={onRequestClose}>
|
||||
<CloseIcon size={40} color="currentColor" />
|
||||
|
@ -38,7 +38,14 @@ const MemberListItem = ({
|
||||
title={user.name}
|
||||
subtitle={
|
||||
<React.Fragment>
|
||||
Joined <Time dateTime={user.createdAt} /> ago
|
||||
{user.lastActiveAt ? (
|
||||
<React.Fragment>
|
||||
Active <Time dateTime={user.lastActiveAt} /> ago
|
||||
</React.Fragment>
|
||||
) : (
|
||||
'Never signed in'
|
||||
)}
|
||||
{!user.lastActiveAt && <Badge>Invited</Badge>}
|
||||
{user.isAdmin && <Badge admin={user.isAdmin}>Admin</Badge>}
|
||||
</React.Fragment>
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { PlusIcon } from 'outline-icons';
|
||||
import Time from 'shared/components/Time';
|
||||
import Avatar from 'components/Avatar';
|
||||
import Button from 'components/Button';
|
||||
import Badge from 'components/Badge';
|
||||
import ListItem from 'components/List/Item';
|
||||
import User from 'models/User';
|
||||
|
||||
@ -17,6 +19,19 @@ const UserListItem = ({ user, onAdd, canEdit }: Props) => {
|
||||
<ListItem
|
||||
title={user.name}
|
||||
image={<Avatar src={user.avatarUrl} size={32} />}
|
||||
subtitle={
|
||||
<React.Fragment>
|
||||
{user.lastActiveAt ? (
|
||||
<React.Fragment>
|
||||
Active <Time dateTime={user.lastActiveAt} /> ago
|
||||
</React.Fragment>
|
||||
) : (
|
||||
'Never signed in'
|
||||
)}
|
||||
{!user.lastActiveAt && <Badge>Invited</Badge>}
|
||||
{user.isAdmin && <Badge admin={user.isAdmin}>Admin</Badge>}
|
||||
</React.Fragment>
|
||||
}
|
||||
actions={
|
||||
canEdit ? (
|
||||
<Button type="button" onClick={onAdd} icon={<PlusIcon />} neutral>
|
||||
|
@ -1,7 +1,7 @@
|
||||
// @flow
|
||||
import * as React from 'react';
|
||||
import { Link, withRouter, type RouterHistory } from 'react-router-dom';
|
||||
import { observable } from 'mobx';
|
||||
import { observable, action } from 'mobx';
|
||||
import { inject, observer } from 'mobx-react';
|
||||
import { CloseIcon } from 'outline-icons';
|
||||
import styled from 'styled-components';
|
||||
@ -30,12 +30,18 @@ type Props = {
|
||||
onSubmit: () => void,
|
||||
};
|
||||
|
||||
type InviteRequest = {
|
||||
email: string,
|
||||
name: string,
|
||||
guest: boolean,
|
||||
};
|
||||
|
||||
@observer
|
||||
class Invite extends React.Component<Props> {
|
||||
@observable isSaving: boolean;
|
||||
@observable linkCopied: boolean = false;
|
||||
@observable
|
||||
invites: { email: string, name: string, guest: boolean }[] = [
|
||||
invites: InviteRequest[] = [
|
||||
{ email: '', name: '', guest: false },
|
||||
{ email: '', name: '', guest: false },
|
||||
{ email: '', name: '', guest: false },
|
||||
@ -56,14 +62,17 @@ class Invite extends React.Component<Props> {
|
||||
}
|
||||
};
|
||||
|
||||
@action
|
||||
handleChange = (ev, index) => {
|
||||
this.invites[index][ev.target.name] = ev.target.value;
|
||||
};
|
||||
|
||||
@action
|
||||
handleGuestChange = (ev, index) => {
|
||||
this.invites[index][ev.target.name] = ev.target.checked;
|
||||
};
|
||||
|
||||
@action
|
||||
handleAdd = () => {
|
||||
if (this.invites.length >= MAX_INVITES) {
|
||||
this.props.ui.showToast(
|
||||
@ -74,6 +83,7 @@ class Invite extends React.Component<Props> {
|
||||
this.invites.push({ email: '', name: '', guest: false });
|
||||
};
|
||||
|
||||
@action
|
||||
handleRemove = (ev: SyntheticEvent<>, index: number) => {
|
||||
ev.preventDefault();
|
||||
this.invites.splice(index, 1);
|
||||
@ -115,7 +125,7 @@ class Invite extends React.Component<Props> {
|
||||
<CopyBlock>
|
||||
Want a link to share directly with your team?
|
||||
<Flex>
|
||||
<Input type="text" value={team.url} flex />
|
||||
<Input type="text" value={team.url} readOnly flex />
|
||||
<CopyToClipboard text={team.url} onCopy={this.handleCopy}>
|
||||
<Button type="button" neutral>
|
||||
{this.linkCopied ? 'Link copied' : 'Copy link'}
|
||||
|
@ -56,7 +56,7 @@ class UserListItem extends React.Component<Props> {
|
||||
Active <Time dateTime={user.lastActiveAt} /> ago
|
||||
</React.Fragment>
|
||||
) : (
|
||||
'Pending'
|
||||
'Invited'
|
||||
)}
|
||||
{user.isAdmin && <Badge admin={user.isAdmin}>Admin</Badge>}
|
||||
{user.isSuspended && <Badge>Suspended</Badge>}
|
||||
|
@ -69,6 +69,9 @@ export default class UsersStore extends BaseStore<User> {
|
||||
invite = async (invites: { email: string, name: string }[]) => {
|
||||
const res = await client.post(`/users.invite`, { invites });
|
||||
invariant(res && res.data, 'Data should be available');
|
||||
runInAction(`invite`, () => {
|
||||
res.data.users.forEach(this.add);
|
||||
});
|
||||
return res.data;
|
||||
};
|
||||
|
||||
|
@ -246,10 +246,13 @@ router.post('users.invite', auth(), async ctx => {
|
||||
const user = ctx.state.user;
|
||||
authorize(user, 'invite', User);
|
||||
|
||||
const invitesSent = await userInviter({ user, invites, ip: ctx.request.ip });
|
||||
const response = await userInviter({ user, invites, ip: ctx.request.ip });
|
||||
|
||||
ctx.body = {
|
||||
data: invitesSent,
|
||||
data: {
|
||||
sent: response.sent,
|
||||
users: response.users.map(user => presentUser(user)),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -14,7 +14,7 @@ export default async function userInviter({
|
||||
user: User,
|
||||
invites: Invite[],
|
||||
ip: string,
|
||||
}): Promise<{ sent: Invite[] }> {
|
||||
}): Promise<{ sent: Invite[], users: User[] }> {
|
||||
const team = await Team.findByPk(user.teamId);
|
||||
|
||||
// filter out empties and obvious non-emails
|
||||
@ -44,12 +44,14 @@ export default async function userInviter({
|
||||
invite => !existingEmails.includes(invite.email)
|
||||
);
|
||||
|
||||
let users = [];
|
||||
|
||||
// send and record remaining invites
|
||||
await Promise.all(
|
||||
filteredInvites.map(async invite => {
|
||||
const transaction = await sequelize.transaction();
|
||||
try {
|
||||
await User.create(
|
||||
const newUser = await User.create(
|
||||
{
|
||||
teamId: user.teamId,
|
||||
name: invite.name,
|
||||
@ -58,6 +60,7 @@ export default async function userInviter({
|
||||
},
|
||||
{ transaction }
|
||||
);
|
||||
users.push(newUser);
|
||||
await Event.create(
|
||||
{
|
||||
name: 'users.invite',
|
||||
@ -88,5 +91,5 @@ export default async function userInviter({
|
||||
})
|
||||
);
|
||||
|
||||
return { sent: filteredInvites };
|
||||
return { sent: filteredInvites, users };
|
||||
}
|
||||
|
Reference in New Issue
Block a user