Files
outline/app/scenes/CollectionPermissions/index.tsx
dependabot[bot] fbd16d4b9a chore(deps-dev): bump prettier from 2.1.2 to 2.8.8 (#5372)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tom Moor <tom.moor@gmail.com>
2023-05-22 19:14:56 -07:00

360 lines
10 KiB
TypeScript

import invariant from "invariant";
import { observer } from "mobx-react";
import { PlusIcon } from "outline-icons";
import * as React from "react";
import { useTranslation, Trans } from "react-i18next";
import styled from "styled-components";
import { CollectionPermission } from "@shared/types";
import Group from "~/models/Group";
import User from "~/models/User";
import Button from "~/components/Button";
import Divider from "~/components/Divider";
import Flex from "~/components/Flex";
import InputSelectPermission from "~/components/InputSelectPermission";
import Labeled from "~/components/Labeled";
import Modal from "~/components/Modal";
import PaginatedList from "~/components/PaginatedList";
import Switch from "~/components/Switch";
import Text from "~/components/Text";
import useBoolean from "~/hooks/useBoolean";
import useCurrentUser from "~/hooks/useCurrentUser";
import useStores from "~/hooks/useStores";
import useToasts from "~/hooks/useToasts";
import AddGroupsToCollection from "./AddGroupsToCollection";
import AddPeopleToCollection from "./AddPeopleToCollection";
import CollectionGroupMemberListItem from "./components/CollectionGroupMemberListItem";
import MemberListItem from "./components/MemberListItem";
type Props = {
collectionId: string;
};
function CollectionPermissions({ collectionId }: Props) {
const { t } = useTranslation();
const user = useCurrentUser();
const {
collections,
memberships,
collectionGroupMemberships,
users,
groups,
auth,
} = useStores();
const { showToast } = useToasts();
const collection = collections.get(collectionId);
invariant(collection, "Collection not found");
const [addGroupModalOpen, handleAddGroupModalOpen, handleAddGroupModalClose] =
useBoolean();
const [
addMemberModalOpen,
handleAddMemberModalOpen,
handleAddMemberModalClose,
] = useBoolean();
const handleRemoveUser = React.useCallback(
async (user) => {
try {
await memberships.delete({
collectionId: collection.id,
userId: user.id,
});
showToast(
t(`{{ userName }} was removed from the collection`, {
userName: user.name,
}),
{
type: "success",
}
);
} catch (err) {
showToast(t("Could not remove user"), {
type: "error",
});
}
},
[memberships, showToast, collection, t]
);
const handleUpdateUser = React.useCallback(
async (user, permission) => {
try {
await memberships.create({
collectionId: collection.id,
userId: user.id,
permission,
});
showToast(
t(`{{ userName }} permissions were updated`, {
userName: user.name,
}),
{
type: "success",
}
);
} catch (err) {
showToast(t("Could not update user"), {
type: "error",
});
}
},
[memberships, showToast, collection, t]
);
const handleRemoveGroup = React.useCallback(
async (group) => {
try {
await collectionGroupMemberships.delete({
collectionId: collection.id,
groupId: group.id,
});
showToast(
t(`The {{ groupName }} group was removed from the collection`, {
groupName: group.name,
}),
{
type: "success",
}
);
} catch (err) {
showToast(t("Could not remove group"), {
type: "error",
});
}
},
[collectionGroupMemberships, showToast, collection, t]
);
const handleUpdateGroup = React.useCallback(
async (group, permission) => {
try {
await collectionGroupMemberships.create({
collectionId: collection.id,
groupId: group.id,
permission,
});
showToast(
t(`{{ groupName }} permissions were updated`, {
groupName: group.name,
}),
{
type: "success",
}
);
} catch (err) {
showToast(t("Could not update user"), {
type: "error",
});
}
},
[collectionGroupMemberships, showToast, collection, t]
);
const handleChangePermission = React.useCallback(
async (permission: CollectionPermission) => {
try {
await collection.save({
permission,
});
showToast(t("Default access permissions were updated"), {
type: "success",
});
} catch (err) {
showToast(t("Could not update permissions"), {
type: "error",
});
}
},
[collection, showToast, t]
);
const fetchOptions = React.useMemo(
() => ({
id: collection.id,
}),
[collection.id]
);
const handleSharingChange = React.useCallback(
async (ev: React.ChangeEvent<HTMLInputElement>) => {
try {
await collection.save({
sharing: ev.target.checked,
});
showToast(t("Public document sharing permissions were updated"), {
type: "success",
});
} catch (err) {
showToast(t("Could not update public document sharing"), {
type: "error",
});
}
},
[collection, showToast, t]
);
const collectionName = collection.name;
const collectionGroups = groups.inCollection(collection.id);
const collectionUsers = users.inCollection(collection.id);
const isEmpty = !collectionGroups.length && !collectionUsers.length;
const sharing = collection.sharing;
const teamSharingEnabled = !!auth.team && auth.team.sharing;
return (
<Flex column>
<InputSelectPermission
onChange={handleChangePermission}
value={collection.permission || ""}
/>
<PermissionExplainer size="small">
{collection.isPrivate && (
<Trans
defaults="The <em>{{ collectionName }}</em> collection is private. Workspace members have no access to it by default."
values={{
collectionName,
}}
components={{
em: <strong />,
}}
/>
)}
{collection.permission === CollectionPermission.ReadWrite && (
<Trans
defaults="Workspace members can view and edit documents in the <em>{{ collectionName }}</em> collection by default."
values={{
collectionName,
}}
components={{
em: <strong />,
}}
/>
)}
{collection.permission === CollectionPermission.Read && (
<Trans
defaults="Workspace members can view documents in the <em>{{ collectionName }}</em> collection by
default."
values={{
collectionName,
}}
components={{
em: <strong />,
}}
/>
)}
</PermissionExplainer>
<Switch
id="sharing"
label={t("Public document sharing")}
onChange={handleSharingChange}
checked={sharing && teamSharingEnabled}
disabled={!teamSharingEnabled}
note={
teamSharingEnabled ? (
<Trans>
When enabled, documents can be shared publicly on the internet.
</Trans>
) : (
<Trans>
Public sharing is currently disabled in the team security
settings.
</Trans>
)
}
/>
<Labeled label={t("Additional access")}>
<Actions gap={8}>
<Button
type="button"
onClick={handleAddGroupModalOpen}
icon={<PlusIcon />}
neutral
>
{t("Add groups")}
</Button>
<Button
type="button"
onClick={handleAddMemberModalOpen}
icon={<PlusIcon />}
neutral
>
{t("Add people")}
</Button>
</Actions>
</Labeled>
<Divider />
{isEmpty && (
<Empty>
<Trans>Add additional access for individual members and groups</Trans>
</Empty>
)}
<PaginatedList
items={collectionGroups}
fetch={collectionGroupMemberships.fetchPage}
options={fetchOptions}
renderItem={(group: Group) => (
<CollectionGroupMemberListItem
key={group.id}
group={group}
collectionGroupMembership={collectionGroupMemberships.get(
`${group.id}-${collection.id}`
)}
onRemove={() => handleRemoveGroup(group)}
onUpdate={(permission) => handleUpdateGroup(group, permission)}
/>
)}
/>
{collectionGroups.length ? <Divider /> : null}
<PaginatedList
key={`collection-users-${collection.permission || "none"}`}
items={collectionUsers}
fetch={memberships.fetchPage}
options={fetchOptions}
renderItem={(item: User) => (
<MemberListItem
key={item.id}
user={item}
membership={memberships.get(`${item.id}-${collection.id}`)}
canEdit={item.id !== user.id || user.isAdmin}
onRemove={() => handleRemoveUser(item)}
onUpdate={(permission) => handleUpdateUser(item, permission)}
/>
)}
/>
<Modal
title={t(`Add groups to {{ collectionName }}`, {
collectionName: collection.name,
})}
onRequestClose={handleAddGroupModalClose}
isOpen={addGroupModalOpen}
>
<AddGroupsToCollection collection={collection} />
</Modal>
<Modal
title={t(`Add people to {{ collectionName }}`, {
collectionName: collection.name,
})}
onRequestClose={handleAddMemberModalClose}
isOpen={addMemberModalOpen}
>
<AddPeopleToCollection collection={collection} />
</Modal>
</Flex>
);
}
const Empty = styled(Text)`
margin-top: 8px;
`;
const PermissionExplainer = styled(Text)`
margin-top: -8px;
margin-bottom: 24px;
`;
const Actions = styled(Flex)`
margin-bottom: 12px;
`;
export default observer(CollectionPermissions);