Document share dialog should allow inviting new users (#6827)
closes #6796
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { isEmail } from "class-validator";
|
||||
import { AnimatePresence, m } from "framer-motion";
|
||||
import { observer } from "mobx-react";
|
||||
import { BackIcon, LinkIcon } from "outline-icons";
|
||||
@@ -150,27 +151,46 @@ function SharePopover({
|
||||
name: t("Invite"),
|
||||
section: UserSection,
|
||||
perform: async () => {
|
||||
await Promise.all(
|
||||
pendingIds.map((userId) => {
|
||||
const user = users.get(userId);
|
||||
const usersInvited = await Promise.all(
|
||||
pendingIds.map(async (idOrEmail) => {
|
||||
let user;
|
||||
|
||||
return userMemberships.create({
|
||||
// convert email to user
|
||||
if (isEmail(idOrEmail)) {
|
||||
const response = await users.invite([
|
||||
{
|
||||
email: idOrEmail,
|
||||
name: idOrEmail,
|
||||
role: team.defaultUserRole,
|
||||
},
|
||||
]);
|
||||
user = response.users[0];
|
||||
} else {
|
||||
user = users.get(idOrEmail);
|
||||
}
|
||||
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
await userMemberships.create({
|
||||
documentId: document.id,
|
||||
userId,
|
||||
userId: user.id,
|
||||
permission:
|
||||
user?.role === UserRole.Viewer ||
|
||||
user?.role === UserRole.Guest
|
||||
? DocumentPermission.Read
|
||||
: DocumentPermission.ReadWrite,
|
||||
});
|
||||
|
||||
return user;
|
||||
})
|
||||
);
|
||||
|
||||
if (pendingIds.length === 1) {
|
||||
const user = users.get(pendingIds[0]);
|
||||
if (usersInvited.length === 1) {
|
||||
toast.message(
|
||||
t("{{ userName }} was invited to the document", {
|
||||
userName: user!.name,
|
||||
userName: usersInvited[0].name,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
@@ -186,7 +206,15 @@ function SharePopover({
|
||||
hidePicker();
|
||||
},
|
||||
}),
|
||||
[document.id, hidePicker, pendingIds, t, users, userMemberships]
|
||||
[
|
||||
t,
|
||||
pendingIds,
|
||||
hidePicker,
|
||||
userMemberships,
|
||||
document.id,
|
||||
users,
|
||||
team.defaultUserRole,
|
||||
]
|
||||
);
|
||||
|
||||
const handleQuery = React.useCallback(
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { isEmail } from "class-validator";
|
||||
import { observer } from "mobx-react";
|
||||
import { CheckmarkIcon, CloseIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import { stringToColor } from "@shared/utils/color";
|
||||
import Document from "~/models/Document";
|
||||
import User from "~/models/User";
|
||||
import useCurrentUser from "~/hooks/useCurrentUser";
|
||||
@@ -11,10 +13,14 @@ import useStores from "~/hooks/useStores";
|
||||
import useThrottledCallback from "~/hooks/useThrottledCallback";
|
||||
import { hover } from "~/styles";
|
||||
import Avatar from "../Avatar";
|
||||
import { AvatarSize } from "../Avatar/Avatar";
|
||||
import { AvatarSize, IAvatar } from "../Avatar/Avatar";
|
||||
import Empty from "../Empty";
|
||||
import { InviteIcon, StyledListItem } from "./MemberListItem";
|
||||
|
||||
type Suggestion = IAvatar & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
/** The document being shared. */
|
||||
document: Document;
|
||||
@@ -35,21 +41,51 @@ export const UserSuggestions = observer(
|
||||
const user = useCurrentUser();
|
||||
|
||||
const fetchUsersByQuery = useThrottledCallback(
|
||||
(query) => users.fetchPage({ query }),
|
||||
(params) => users.fetchPage({ query: params.query }),
|
||||
250
|
||||
);
|
||||
|
||||
const suggestions = React.useMemo(
|
||||
() =>
|
||||
users
|
||||
.notInDocument(document.id, query)
|
||||
.filter((u) => u.id !== user.id && !u.isSuspended),
|
||||
[users, users.orderedData, document.id, document.members, user.id, query]
|
||||
const getSuggestionForEmail = React.useCallback(
|
||||
(email: string) => ({
|
||||
id: email,
|
||||
name: email,
|
||||
avatarUrl: "",
|
||||
color: stringToColor(email),
|
||||
initial: email[0].toUpperCase(),
|
||||
email: t("Invite to workspace"),
|
||||
}),
|
||||
[t]
|
||||
);
|
||||
|
||||
const suggestions = React.useMemo(() => {
|
||||
const filtered: Suggestion[] = users
|
||||
.notInDocument(document.id, query)
|
||||
.filter((u) => u.id !== user.id && !u.isSuspended);
|
||||
|
||||
if (isEmail(query)) {
|
||||
filtered.push(getSuggestionForEmail(query));
|
||||
}
|
||||
|
||||
return filtered;
|
||||
}, [
|
||||
getSuggestionForEmail,
|
||||
users,
|
||||
users.orderedData,
|
||||
document.id,
|
||||
document.members,
|
||||
user.id,
|
||||
query,
|
||||
t,
|
||||
]);
|
||||
|
||||
const pending = React.useMemo(
|
||||
() => pendingIds.map((id) => users.get(id)).filter(Boolean) as User[],
|
||||
[users, pendingIds]
|
||||
() =>
|
||||
pendingIds
|
||||
.map((id) =>
|
||||
isEmail(id) ? getSuggestionForEmail(id) : users.get(id)
|
||||
)
|
||||
.filter(Boolean) as User[],
|
||||
[users, getSuggestionForEmail, pendingIds]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -100,7 +136,7 @@ export const UserSuggestions = observer(
|
||||
(suggestionsWithPending.length > 0 || isEmpty) && <Separator />}
|
||||
{suggestionsWithPending.map((suggestion) => (
|
||||
<StyledListItem
|
||||
{...getListItemProps(suggestion)}
|
||||
{...getListItemProps(suggestion as User)}
|
||||
key={suggestion.id}
|
||||
onClick={() => addPendingId(suggestion.id)}
|
||||
actions={<InviteIcon />}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { computed, observable } from "mobx";
|
||||
import { TeamPreferenceDefaults } from "@shared/constants";
|
||||
import { TeamPreference, TeamPreferences } from "@shared/types";
|
||||
import { TeamPreference, TeamPreferences, UserRole } from "@shared/types";
|
||||
import { stringToColor } from "@shared/utils/color";
|
||||
import Model from "./base/Model";
|
||||
import Field from "./decorators/Field";
|
||||
@@ -58,7 +58,7 @@ class Team extends Model {
|
||||
|
||||
@Field
|
||||
@observable
|
||||
defaultUserRole: string;
|
||||
defaultUserRole: UserRole;
|
||||
|
||||
@Field
|
||||
@observable
|
||||
|
||||
Reference in New Issue
Block a user