Skip to content

Commit

Permalink
Document share dialog should allow inviting new users (#6827)
Browse files Browse the repository at this point in the history
closes #6796
  • Loading branch information
tommoor committed Apr 23, 2024
1 parent 3dbb710 commit a60e464
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 22 deletions.
46 changes: 37 additions & 9 deletions app/components/Sharing/SharePopover.tsx
@@ -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";
Expand Down Expand Up @@ -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 {
Expand All @@ -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(
Expand Down
58 changes: 47 additions & 11 deletions app/components/Sharing/UserSuggestions.tsx
@@ -1,20 +1,26 @@
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";
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;
Expand All @@ -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(() => {
Expand Down Expand Up @@ -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 />}
Expand Down
4 changes: 2 additions & 2 deletions app/models/Team.ts
@@ -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";
Expand Down Expand Up @@ -58,7 +58,7 @@ class Team extends Model {

@Field
@observable
defaultUserRole: string;
defaultUserRole: UserRole;

@Field
@observable
Expand Down

0 comments on commit a60e464

Please sign in to comment.