feat: Adds permission selector in document/collection invite flow (#6948)
* stash * fix: Permissions cleared on collection addition fix: Cannot remove user from document Allow choosing permission in invite flow
This commit is contained in:
@@ -10,19 +10,13 @@ export default function InputMemberPermissionSelect(
|
||||
) {
|
||||
const { value, onChange, ...rest } = props;
|
||||
const { t } = useTranslation();
|
||||
const handleChange = React.useCallback(
|
||||
(value) => {
|
||||
onChange?.(value === EmptySelectValue ? null : value);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<Select
|
||||
label={t("Permissions")}
|
||||
options={props.permissions}
|
||||
ariaLabel={t("Permissions")}
|
||||
onChange={handleChange}
|
||||
onChange={onChange}
|
||||
value={value || EmptySelectValue}
|
||||
labelHidden
|
||||
nude
|
||||
|
||||
@@ -37,7 +37,7 @@ export type Props = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
value?: string | null;
|
||||
label?: string;
|
||||
label?: React.ReactNode;
|
||||
nude?: boolean;
|
||||
ariaLabel: string;
|
||||
short?: boolean;
|
||||
|
||||
@@ -17,12 +17,6 @@ function InputSelectPermission(
|
||||
) {
|
||||
const { value, onChange, ...rest } = props;
|
||||
const { t } = useTranslation();
|
||||
const handleChange = React.useCallback(
|
||||
(value) => {
|
||||
onChange?.(value === EmptySelectValue ? null : value);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<InputSelect
|
||||
@@ -44,7 +38,7 @@ function InputSelectPermission(
|
||||
]}
|
||||
ariaLabel={t("Default access")}
|
||||
value={value || EmptySelectValue}
|
||||
onChange={handleChange}
|
||||
onChange={onChange}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -5,16 +5,19 @@ import { BackIcon, LinkIcon, UserIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { useTheme } from "styled-components";
|
||||
import styled, { useTheme } from "styled-components";
|
||||
import Flex from "@shared/components/Flex";
|
||||
import Squircle from "@shared/components/Squircle";
|
||||
import { CollectionPermission, UserRole } from "@shared/types";
|
||||
import { CollectionPermission } from "@shared/types";
|
||||
import Collection from "~/models/Collection";
|
||||
import Group from "~/models/Group";
|
||||
import Share from "~/models/Share";
|
||||
import User from "~/models/User";
|
||||
import Avatar, { AvatarSize } from "~/components/Avatar/Avatar";
|
||||
import { Inner } from "~/components/Button";
|
||||
import ButtonSmall from "~/components/ButtonSmall";
|
||||
import CopyToClipboard from "~/components/CopyToClipboard";
|
||||
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
|
||||
import InputSelectPermission from "~/components/InputSelectPermission";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
@@ -26,6 +29,7 @@ import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useKeyDown from "~/hooks/useKeyDown";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { Permission } from "~/types";
|
||||
import { collectionPath, urlify } from "~/utils/routeHelpers";
|
||||
import { Wrapper, presence } from "../components";
|
||||
import { ListItem } from "../components/ListItem";
|
||||
@@ -55,6 +59,9 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
|
||||
const [hasRendered, setHasRendered] = React.useState(visible);
|
||||
const [pendingIds, setPendingIds] = React.useState<string[]>([]);
|
||||
const [invitedInSession, setInvitedInSession] = React.useState<string[]>([]);
|
||||
const [permission, setPermission] = React.useState<CollectionPermission>(
|
||||
CollectionPermission.Read
|
||||
);
|
||||
const timeout = React.useRef<ReturnType<typeof setTimeout>>();
|
||||
const context = useActionContext();
|
||||
|
||||
@@ -153,11 +160,7 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
|
||||
await memberships.create({
|
||||
collectionId: collection.id,
|
||||
userId: user.id,
|
||||
permission:
|
||||
user.role === UserRole.Viewer ||
|
||||
user.role === UserRole.Guest
|
||||
? CollectionPermission.Read
|
||||
: CollectionPermission.ReadWrite,
|
||||
permission,
|
||||
});
|
||||
return user;
|
||||
}
|
||||
@@ -225,15 +228,37 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
|
||||
}),
|
||||
[
|
||||
collection.id,
|
||||
collectionGroupMemberships,
|
||||
groups,
|
||||
hidePicker,
|
||||
memberships,
|
||||
pendingIds,
|
||||
permission,
|
||||
t,
|
||||
team.defaultUserRole,
|
||||
users,
|
||||
]
|
||||
);
|
||||
|
||||
const permissions = React.useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
label: t("Admin"),
|
||||
value: CollectionPermission.Admin,
|
||||
},
|
||||
{
|
||||
label: t("Can edit"),
|
||||
value: CollectionPermission.ReadWrite,
|
||||
},
|
||||
{
|
||||
label: t("View only"),
|
||||
value: CollectionPermission.Read,
|
||||
},
|
||||
] as Permission[],
|
||||
[t]
|
||||
);
|
||||
|
||||
if (!hasRendered) {
|
||||
return null;
|
||||
}
|
||||
@@ -259,9 +284,18 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
|
||||
|
||||
const rightButton = picker ? (
|
||||
pendingIds.length ? (
|
||||
<ButtonSmall action={inviteAction} context={context} key="invite">
|
||||
{t("Add")}
|
||||
</ButtonSmall>
|
||||
<Flex gap={4} key="invite">
|
||||
<InputPermissionSelect
|
||||
permissions={permissions}
|
||||
onChange={(value: CollectionPermission) => setPermission(value)}
|
||||
value={permission}
|
||||
labelHidden
|
||||
nude
|
||||
/>
|
||||
<ButtonSmall action={inviteAction} context={context}>
|
||||
{t("Add")}
|
||||
</ButtonSmall>
|
||||
</Flex>
|
||||
) : null
|
||||
) : (
|
||||
<Tooltip
|
||||
@@ -339,4 +373,14 @@ function SharePopover({ collection, visible, onRequestClose }: Props) {
|
||||
);
|
||||
}
|
||||
|
||||
const InputPermissionSelect = styled(InputMemberPermissionSelect)`
|
||||
font-size: 13px;
|
||||
height: 26px;
|
||||
|
||||
${Inner} {
|
||||
line-height: 26px;
|
||||
min-height: 26px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default observer(SharePopover);
|
||||
|
||||
@@ -52,6 +52,7 @@ const DocumentMemberListItem = ({
|
||||
value: DocumentPermission.ReadWrite,
|
||||
},
|
||||
{
|
||||
divider: true,
|
||||
label: t("Remove"),
|
||||
value: EmptySelectValue,
|
||||
},
|
||||
|
||||
@@ -5,14 +5,18 @@ import { BackIcon, LinkIcon } from "outline-icons";
|
||||
import * as React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { toast } from "sonner";
|
||||
import { DocumentPermission, UserRole } from "@shared/types";
|
||||
import styled from "styled-components";
|
||||
import Flex from "@shared/components/Flex";
|
||||
import { DocumentPermission } from "@shared/types";
|
||||
import Document from "~/models/Document";
|
||||
import Share from "~/models/Share";
|
||||
import User from "~/models/User";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import { AvatarSize } from "~/components/Avatar/Avatar";
|
||||
import { Inner } from "~/components/Button";
|
||||
import ButtonSmall from "~/components/ButtonSmall";
|
||||
import CopyToClipboard from "~/components/CopyToClipboard";
|
||||
import InputMemberPermissionSelect from "~/components/InputMemberPermissionSelect";
|
||||
import NudeButton from "~/components/NudeButton";
|
||||
import Tooltip from "~/components/Tooltip";
|
||||
import { createAction } from "~/actions";
|
||||
@@ -23,6 +27,7 @@ import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||
import useKeyDown from "~/hooks/useKeyDown";
|
||||
import usePolicy from "~/hooks/usePolicy";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { Permission } from "~/types";
|
||||
import { documentPath, urlify } from "~/utils/routeHelpers";
|
||||
import { Separator, Wrapper, presence } from "../components";
|
||||
import { SearchInput } from "../components/SearchInput";
|
||||
@@ -64,6 +69,9 @@ function SharePopover({
|
||||
const [invitedInSession, setInvitedInSession] = React.useState<string[]>([]);
|
||||
const [pendingIds, setPendingIds] = React.useState<string[]>([]);
|
||||
const collectionSharingDisabled = document.collection?.sharing === false;
|
||||
const [permission, setPermission] = React.useState<DocumentPermission>(
|
||||
DocumentPermission.Read
|
||||
);
|
||||
|
||||
useKeyDown(
|
||||
"Escape",
|
||||
@@ -150,11 +158,7 @@ function SharePopover({
|
||||
await userMemberships.create({
|
||||
documentId: document.id,
|
||||
userId: user.id,
|
||||
permission:
|
||||
user?.role === UserRole.Viewer ||
|
||||
user?.role === UserRole.Guest
|
||||
? DocumentPermission.Read
|
||||
: DocumentPermission.ReadWrite,
|
||||
permission,
|
||||
});
|
||||
|
||||
return user;
|
||||
@@ -190,6 +194,7 @@ function SharePopover({
|
||||
hidePicker,
|
||||
userMemberships,
|
||||
document.id,
|
||||
permission,
|
||||
users,
|
||||
team.defaultUserRole,
|
||||
]
|
||||
@@ -217,6 +222,21 @@ function SharePopover({
|
||||
[setPendingIds]
|
||||
);
|
||||
|
||||
const permissions = React.useMemo(
|
||||
() =>
|
||||
[
|
||||
{
|
||||
label: t("Can edit"),
|
||||
value: DocumentPermission.ReadWrite,
|
||||
},
|
||||
{
|
||||
label: t("View only"),
|
||||
value: DocumentPermission.Read,
|
||||
},
|
||||
] as Permission[],
|
||||
[t]
|
||||
);
|
||||
|
||||
if (!hasRendered) {
|
||||
return null;
|
||||
}
|
||||
@@ -242,9 +262,18 @@ function SharePopover({
|
||||
|
||||
const rightButton = picker ? (
|
||||
pendingIds.length ? (
|
||||
<ButtonSmall action={inviteAction} context={context} key="invite">
|
||||
{t("Add")}
|
||||
</ButtonSmall>
|
||||
<Flex gap={4} key="invite">
|
||||
<InputPermissionSelect
|
||||
permissions={permissions}
|
||||
onChange={(value: DocumentPermission) => setPermission(value)}
|
||||
value={permission}
|
||||
labelHidden
|
||||
nude
|
||||
/>
|
||||
<ButtonSmall action={inviteAction} context={context}>
|
||||
{t("Add")}
|
||||
</ButtonSmall>
|
||||
</Flex>
|
||||
) : null
|
||||
) : (
|
||||
<Tooltip
|
||||
@@ -312,4 +341,14 @@ function SharePopover({
|
||||
);
|
||||
}
|
||||
|
||||
const InputPermissionSelect = styled(InputMemberPermissionSelect)`
|
||||
font-size: 13px;
|
||||
height: 26px;
|
||||
|
||||
${Inner} {
|
||||
line-height: 26px;
|
||||
min-height: 26px;
|
||||
}
|
||||
`;
|
||||
|
||||
export default observer(SharePopover);
|
||||
|
||||
@@ -416,12 +416,12 @@ class WebsocketProvider extends React.Component<Props> {
|
||||
await collections.fetch(event.collectionId, {
|
||||
force: true,
|
||||
});
|
||||
}
|
||||
|
||||
// Document policies might need updating as the permission changes
|
||||
documents.inCollection(event.collectionId).forEach((document) => {
|
||||
policies.remove(document.id);
|
||||
});
|
||||
// Document policies might need updating as the permission changes
|
||||
documents.inCollection(event.collectionId).forEach((document) => {
|
||||
policies.remove(document.id);
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user