chore: Remove old collection permissions UI (#6995)
This commit is contained in:
@@ -11,7 +11,6 @@ import {
|
||||
import * as React from "react";
|
||||
import stores from "~/stores";
|
||||
import Collection from "~/models/Collection";
|
||||
import CollectionPermissions from "~/scenes/CollectionPermissions";
|
||||
import { CollectionEdit } from "~/components/Collection/CollectionEdit";
|
||||
import { CollectionNew } from "~/components/Collection/CollectionNew";
|
||||
import CollectionDeleteDialog from "~/components/CollectionDeleteDialog";
|
||||
@@ -21,7 +20,6 @@ import { getHeaderExpandedKey } from "~/components/Sidebar/components/Header";
|
||||
import { createAction } from "~/actions";
|
||||
import { CollectionSection } from "~/actions/sections";
|
||||
import { setPersistedState } from "~/hooks/usePersistedState";
|
||||
import { Feature, FeatureFlags } from "~/utils/FeatureFlags";
|
||||
import history from "~/utils/history";
|
||||
import { searchPath } from "~/utils/routeHelpers";
|
||||
|
||||
@@ -111,24 +109,16 @@ export const editCollectionPermissions = createAction({
|
||||
return;
|
||||
}
|
||||
|
||||
if (FeatureFlags.isEnabled(Feature.newCollectionSharing)) {
|
||||
stores.dialogs.openModal({
|
||||
title: t("Share this collection"),
|
||||
content: (
|
||||
<SharePopover
|
||||
collection={collection}
|
||||
onRequestClose={stores.dialogs.closeAllModals}
|
||||
visible
|
||||
/>
|
||||
),
|
||||
});
|
||||
} else {
|
||||
stores.dialogs.openModal({
|
||||
title: t("Collection permissions"),
|
||||
fullscreen: true,
|
||||
content: <CollectionPermissions collectionId={activeCollectionId} />,
|
||||
});
|
||||
}
|
||||
stores.dialogs.openModal({
|
||||
title: t("Share this collection"),
|
||||
content: (
|
||||
<SharePopover
|
||||
collection={collection}
|
||||
onRequestClose={stores.dialogs.closeAllModals}
|
||||
visible
|
||||
/>
|
||||
),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
import debounce from "lodash/debounce";
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import styled from "styled-components";
|
||||
import Collection from "~/models/Collection";
|
||||
import Group from "~/models/Group";
|
||||
import GroupNew from "~/scenes/GroupNew";
|
||||
import Button from "~/components/Button";
|
||||
import ButtonLink from "~/components/ButtonLink";
|
||||
import Empty from "~/components/Empty";
|
||||
import Flex from "~/components/Flex";
|
||||
import GroupListItem from "~/components/GroupListItem";
|
||||
import Input from "~/components/Input";
|
||||
import Modal from "~/components/Modal";
|
||||
import PaginatedList from "~/components/PaginatedList";
|
||||
import Text from "~/components/Text";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useStores from "~/hooks/useStores";
|
||||
|
||||
type Props = {
|
||||
collection: Collection;
|
||||
};
|
||||
|
||||
function AddGroupsToCollection(props: Props) {
|
||||
const { collection } = props;
|
||||
|
||||
const [newGroupModalOpen, handleNewGroupModalOpen, handleNewGroupModalClose] =
|
||||
useBoolean(false);
|
||||
const [query, setQuery] = React.useState("");
|
||||
const team = useCurrentTeam();
|
||||
const { collectionGroupMemberships, groups, policies } = useStores();
|
||||
const { t } = useTranslation();
|
||||
const { fetchPage: fetchGroups } = groups;
|
||||
const can = policies.abilities(team.id);
|
||||
|
||||
const debouncedFetch = React.useMemo(
|
||||
() => debounce((query) => fetchGroups({ query }), 250),
|
||||
[fetchGroups]
|
||||
);
|
||||
|
||||
const handleFilter = React.useCallback(
|
||||
(ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const updatedQuery = ev.target.value;
|
||||
setQuery(updatedQuery);
|
||||
void debouncedFetch(updatedQuery);
|
||||
},
|
||||
[debouncedFetch]
|
||||
);
|
||||
|
||||
const handleAddGroup = async (group: Group) => {
|
||||
try {
|
||||
await collectionGroupMemberships.create({
|
||||
collectionId: collection.id,
|
||||
groupId: group.id,
|
||||
});
|
||||
toast.success(
|
||||
t("{{ groupName }} was added to the collection", {
|
||||
groupName: group.name,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not add user"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex column>
|
||||
{can.createGroup ? (
|
||||
<Text as="p" type="secondary">
|
||||
{t("Can’t find the group you’re looking for?")}{" "}
|
||||
<ButtonLink onClick={handleNewGroupModalOpen}>
|
||||
{t("Create a group")}
|
||||
</ButtonLink>
|
||||
.
|
||||
</Text>
|
||||
) : null}
|
||||
|
||||
<Input
|
||||
type="search"
|
||||
placeholder={`${t("Search by group name")}…`}
|
||||
value={query}
|
||||
onChange={handleFilter}
|
||||
label={t("Search groups")}
|
||||
labelHidden
|
||||
flex
|
||||
/>
|
||||
<PaginatedList
|
||||
empty={
|
||||
query ? (
|
||||
<Empty>{t("No groups matching your search")}</Empty>
|
||||
) : (
|
||||
<Empty>{t("No groups left to add")}</Empty>
|
||||
)
|
||||
}
|
||||
items={groups.notInCollection(collection.id, query)}
|
||||
fetch={query ? undefined : fetchGroups}
|
||||
renderItem={(item: Group) => (
|
||||
<GroupListItem
|
||||
key={item.id}
|
||||
group={item}
|
||||
showFacepile
|
||||
renderActions={() => (
|
||||
<ButtonWrap>
|
||||
<Button onClick={() => handleAddGroup(item)} neutral>
|
||||
{t("Add")}
|
||||
</Button>
|
||||
</ButtonWrap>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{can.createGroup ? (
|
||||
<Modal
|
||||
title={t("Create a group")}
|
||||
onRequestClose={handleNewGroupModalClose}
|
||||
isOpen={newGroupModalOpen}
|
||||
>
|
||||
<GroupNew onSubmit={handleNewGroupModalClose} />
|
||||
</Modal>
|
||||
) : null}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
const ButtonWrap = styled.div`
|
||||
margin-left: 6px;
|
||||
`;
|
||||
|
||||
export default observer(AddGroupsToCollection);
|
||||
@@ -1,130 +0,0 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { CollectionPermission, UserRole } from "@shared/types";
|
||||
import Collection from "~/models/Collection";
|
||||
import User from "~/models/User";
|
||||
import Invite from "~/scenes/Invite";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import { AvatarSize } from "~/components/Avatar/Avatar";
|
||||
import ButtonLink from "~/components/ButtonLink";
|
||||
import Empty from "~/components/Empty";
|
||||
import Flex from "~/components/Flex";
|
||||
import Input from "~/components/Input";
|
||||
import Modal from "~/components/Modal";
|
||||
import PaginatedList from "~/components/PaginatedList";
|
||||
import Text from "~/components/Text";
|
||||
import useBoolean from "~/hooks/useBoolean";
|
||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import useThrottledCallback from "~/hooks/useThrottledCallback";
|
||||
import MemberListItem from "./components/MemberListItem";
|
||||
|
||||
type Props = {
|
||||
collection: Collection;
|
||||
};
|
||||
|
||||
function AddPeopleToCollection({ collection }: Props) {
|
||||
const { memberships, users } = useStores();
|
||||
const team = useCurrentTeam();
|
||||
const { t } = useTranslation();
|
||||
const can = usePolicy(team);
|
||||
|
||||
const [inviteModalOpen, setInviteModalOpen, setInviteModalClosed] =
|
||||
useBoolean();
|
||||
const [query, setQuery] = React.useState("");
|
||||
|
||||
const debouncedFetch = useThrottledCallback(
|
||||
(query) =>
|
||||
users.fetchPage({
|
||||
query,
|
||||
}),
|
||||
250
|
||||
);
|
||||
|
||||
const handleFilter = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setQuery(ev.target.value);
|
||||
void debouncedFetch(ev.target.value);
|
||||
};
|
||||
|
||||
const handleAddUser = async (user: User) => {
|
||||
try {
|
||||
await memberships.create({
|
||||
permission:
|
||||
user.role === UserRole.Viewer || user.role === UserRole.Guest
|
||||
? CollectionPermission.Read
|
||||
: CollectionPermission.ReadWrite,
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
});
|
||||
toast.success(
|
||||
t("{{ userName }} was added to the collection", {
|
||||
userName: user.name,
|
||||
}),
|
||||
{
|
||||
icon: <Avatar model={user} size={AvatarSize.Toast} />,
|
||||
}
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not add user"));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Flex column>
|
||||
<Text as="p" type="secondary">
|
||||
{t("Need to add someone who’s not on the team yet?")}{" "}
|
||||
{can.inviteUser ? (
|
||||
<ButtonLink onClick={setInviteModalOpen}>
|
||||
{t("Invite people to {{ teamName }}", {
|
||||
teamName: team.name,
|
||||
})}
|
||||
</ButtonLink>
|
||||
) : (
|
||||
t("Ask an admin to invite them first")
|
||||
)}
|
||||
.
|
||||
</Text>
|
||||
<Input
|
||||
type="search"
|
||||
placeholder={`${t("Search by name")}…`}
|
||||
value={query}
|
||||
onChange={handleFilter}
|
||||
label={t("Search people")}
|
||||
autoFocus
|
||||
labelHidden
|
||||
flex
|
||||
/>
|
||||
<PaginatedList
|
||||
empty={
|
||||
query ? (
|
||||
<Empty>{t("No people matching your search")}</Empty>
|
||||
) : (
|
||||
<Empty>{t("No people left to add")}</Empty>
|
||||
)
|
||||
}
|
||||
items={users.notInCollection(collection.id, query)}
|
||||
fetch={query ? undefined : users.fetchPage}
|
||||
renderItem={(item: User) => (
|
||||
<MemberListItem
|
||||
key={item.id}
|
||||
user={item}
|
||||
onAdd={() => handleAddUser(item)}
|
||||
canEdit
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Modal
|
||||
title={t("Invite people")}
|
||||
onRequestClose={setInviteModalClosed}
|
||||
isOpen={inviteModalOpen}
|
||||
>
|
||||
<Invite onSubmit={setInviteModalClosed} />
|
||||
</Modal>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
export default observer(AddPeopleToCollection);
|
||||
@@ -1,59 +0,0 @@
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import CollectionGroupMembership from "~/models/CollectionGroupMembership";
|
||||
import Group from "~/models/Group";
|
||||
import GroupListItem from "~/components/GroupListItem";
|
||||
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
|
||||
import CollectionGroupMemberMenu from "~/menus/CollectionGroupMemberMenu";
|
||||
|
||||
type Props = {
|
||||
group: Group;
|
||||
collectionGroupMembership: CollectionGroupMembership | null | undefined;
|
||||
onUpdate: (permission: CollectionPermission) => void;
|
||||
onRemove: () => void;
|
||||
};
|
||||
|
||||
const CollectionGroupMemberListItem = ({
|
||||
group,
|
||||
collectionGroupMembership,
|
||||
onUpdate,
|
||||
onRemove,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<GroupListItem
|
||||
group={group}
|
||||
showAvatar
|
||||
renderActions={({ openMembersModal }) => (
|
||||
<>
|
||||
<InputMemberPermissionSelect
|
||||
value={collectionGroupMembership?.permission}
|
||||
onChange={onUpdate}
|
||||
permissions={[
|
||||
{
|
||||
label: t("View only"),
|
||||
value: CollectionPermission.Read,
|
||||
},
|
||||
{
|
||||
label: t("Can edit"),
|
||||
value: CollectionPermission.ReadWrite,
|
||||
},
|
||||
{
|
||||
label: t("Admin"),
|
||||
value: CollectionPermission.Admin,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<CollectionGroupMemberMenu
|
||||
onMembers={openMembersModal}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default CollectionGroupMemberListItem;
|
||||
@@ -1,92 +0,0 @@
|
||||
import { observer } from "mobx-react";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import Membership from "~/models/Membership";
|
||||
import User from "~/models/User";
|
||||
import UserMembership from "~/models/UserMembership";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import Badge from "~/components/Badge";
|
||||
import Button from "~/components/Button";
|
||||
import Flex from "~/components/Flex";
|
||||
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
|
||||
import ListItem from "~/components/List/Item";
|
||||
import Time from "~/components/Time";
|
||||
import MemberMenu from "~/menus/MemberMenu";
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
membership?: Membership | UserMembership | undefined;
|
||||
canEdit: boolean;
|
||||
onAdd?: () => void;
|
||||
onRemove?: () => void;
|
||||
onUpdate?: (permission: CollectionPermission) => void;
|
||||
};
|
||||
|
||||
const MemberListItem = ({
|
||||
user,
|
||||
membership,
|
||||
onRemove,
|
||||
onUpdate,
|
||||
onAdd,
|
||||
canEdit,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
title={user.name}
|
||||
subtitle={
|
||||
<>
|
||||
{user.lastActiveAt ? (
|
||||
<Trans>
|
||||
Active <Time dateTime={user.lastActiveAt} /> ago
|
||||
</Trans>
|
||||
) : (
|
||||
t("Never signed in")
|
||||
)}
|
||||
{user.isInvited && <Badge>{t("Invited")}</Badge>}
|
||||
{user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>}
|
||||
</>
|
||||
}
|
||||
image={<Avatar model={user} size={32} />}
|
||||
actions={
|
||||
<Flex align="center" gap={8}>
|
||||
{onUpdate && (
|
||||
<InputMemberPermissionSelect
|
||||
permissions={[
|
||||
{
|
||||
label: t("View only"),
|
||||
value: CollectionPermission.Read,
|
||||
},
|
||||
{
|
||||
label: t("Can edit"),
|
||||
value: CollectionPermission.ReadWrite,
|
||||
},
|
||||
{
|
||||
label: t("Admin"),
|
||||
value: CollectionPermission.Admin,
|
||||
},
|
||||
]}
|
||||
value={membership?.permission}
|
||||
onChange={onUpdate}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
)}
|
||||
{canEdit && (
|
||||
<>
|
||||
{onRemove && <MemberMenu user={user} onRemove={onRemove} />}
|
||||
{onAdd && (
|
||||
<Button onClick={onAdd} neutral>
|
||||
{t("Add")}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(MemberListItem);
|
||||
@@ -1,49 +0,0 @@
|
||||
import { observer } from "mobx-react";
|
||||
import { PlusIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import User from "~/models/User";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import Badge from "~/components/Badge";
|
||||
import Button from "~/components/Button";
|
||||
import ListItem from "~/components/List/Item";
|
||||
import Time from "~/components/Time";
|
||||
|
||||
type Props = {
|
||||
user: User;
|
||||
canEdit: boolean;
|
||||
onAdd: () => void;
|
||||
};
|
||||
|
||||
const UserListItem = ({ user, onAdd, canEdit }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
title={user.name}
|
||||
image={<Avatar model={user} size={32} />}
|
||||
subtitle={
|
||||
<>
|
||||
{user.lastActiveAt ? (
|
||||
<Trans>
|
||||
Active <Time dateTime={user.lastActiveAt} /> ago
|
||||
</Trans>
|
||||
) : (
|
||||
t("Never signed in")
|
||||
)}
|
||||
{user.isInvited && <Badge>{t("Invited")}</Badge>}
|
||||
{user.isAdmin && <Badge primary={user.isAdmin}>{t("Admin")}</Badge>}
|
||||
</>
|
||||
}
|
||||
actions={
|
||||
canEdit ? (
|
||||
<Button type="button" onClick={onAdd} icon={<PlusIcon />} neutral>
|
||||
{t("Add")}
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default observer(UserListItem);
|
||||
@@ -1,334 +0,0 @@
|
||||
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 { toast } from "sonner";
|
||||
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 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 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,
|
||||
});
|
||||
toast.success(
|
||||
t(`{{ userName }} was removed from the collection`, {
|
||||
userName: user.name,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not remove user"));
|
||||
}
|
||||
},
|
||||
[memberships, collection, t]
|
||||
);
|
||||
|
||||
const handleUpdateUser = React.useCallback(
|
||||
async (user, permission) => {
|
||||
try {
|
||||
await memberships.create({
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
permission,
|
||||
});
|
||||
toast.success(
|
||||
t(`{{ userName }} permissions were updated`, {
|
||||
userName: user.name,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not update user"));
|
||||
}
|
||||
},
|
||||
[memberships, collection, t]
|
||||
);
|
||||
|
||||
const handleRemoveGroup = React.useCallback(
|
||||
async (group) => {
|
||||
try {
|
||||
await collectionGroupMemberships.delete({
|
||||
collectionId: collection.id,
|
||||
groupId: group.id,
|
||||
});
|
||||
toast.success(
|
||||
t(`The {{ groupName }} group was removed from the collection`, {
|
||||
groupName: group.name,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not remove group"));
|
||||
}
|
||||
},
|
||||
[collectionGroupMemberships, collection, t]
|
||||
);
|
||||
|
||||
const handleUpdateGroup = React.useCallback(
|
||||
async (group, permission) => {
|
||||
try {
|
||||
await collectionGroupMemberships.create({
|
||||
collectionId: collection.id,
|
||||
groupId: group.id,
|
||||
permission,
|
||||
});
|
||||
toast.success(
|
||||
t(`{{ groupName }} permissions were updated`, {
|
||||
groupName: group.name,
|
||||
})
|
||||
);
|
||||
} catch (err) {
|
||||
toast.error(t("Could not update user"));
|
||||
}
|
||||
},
|
||||
[collectionGroupMemberships, collection, t]
|
||||
);
|
||||
|
||||
const handleChangePermission = React.useCallback(
|
||||
async (permission: CollectionPermission) => {
|
||||
try {
|
||||
await collection.save({
|
||||
permission,
|
||||
});
|
||||
toast.success(t("Default access permissions were updated"));
|
||||
} catch (err) {
|
||||
toast.error(t("Could not update permissions"));
|
||||
}
|
||||
},
|
||||
[collection, 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,
|
||||
});
|
||||
toast.success(t("Public document sharing permissions were updated"));
|
||||
} catch (err) {
|
||||
toast.error(t("Could not update public document sharing"));
|
||||
}
|
||||
},
|
||||
[collection, 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 workspace 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.find({
|
||||
collectionId: collection.id,
|
||||
groupId: group.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.find({
|
||||
collectionId: collection.id,
|
||||
userId: item.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);
|
||||
Reference in New Issue
Block a user