Quality of life improvements on 'Invite' screen
This commit is contained in:
@@ -18,7 +18,7 @@ const InputSelectRole = (
|
|||||||
label={t("Role")}
|
label={t("Role")}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
label: t("Member"),
|
label: t("Editor"),
|
||||||
value: "member",
|
value: "member",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { LinkIcon, CloseIcon } from "outline-icons";
|
import { CloseIcon, CopyIcon } from "outline-icons";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { useTranslation, Trans } from "react-i18next";
|
import { useTranslation, Trans } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
@@ -14,6 +14,7 @@ import Flex from "~/components/Flex";
|
|||||||
import Input from "~/components/Input";
|
import Input from "~/components/Input";
|
||||||
import InputSelectRole from "~/components/InputSelectRole";
|
import InputSelectRole from "~/components/InputSelectRole";
|
||||||
import NudeButton from "~/components/NudeButton";
|
import NudeButton from "~/components/NudeButton";
|
||||||
|
import { ResizingHeightContainer } from "~/components/ResizingHeightContainer";
|
||||||
import Text from "~/components/Text";
|
import Text from "~/components/Text";
|
||||||
import Tooltip from "~/components/Tooltip";
|
import Tooltip from "~/components/Tooltip";
|
||||||
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
import useCurrentTeam from "~/hooks/useCurrentTeam";
|
||||||
@@ -33,23 +34,12 @@ type InviteRequest = {
|
|||||||
|
|
||||||
function Invite({ onSubmit }: Props) {
|
function Invite({ onSubmit }: Props) {
|
||||||
const [isSaving, setIsSaving] = React.useState(false);
|
const [isSaving, setIsSaving] = React.useState(false);
|
||||||
const [linkCopied, setLinkCopied] = React.useState<boolean>(false);
|
|
||||||
const [invites, setInvites] = React.useState<InviteRequest[]>([
|
const [invites, setInvites] = React.useState<InviteRequest[]>([
|
||||||
{
|
{
|
||||||
email: "",
|
email: "",
|
||||||
name: "",
|
name: "",
|
||||||
role: UserRole.Member,
|
role: UserRole.Member,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
email: "",
|
|
||||||
name: "",
|
|
||||||
role: UserRole.Member,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
email: "",
|
|
||||||
name: "",
|
|
||||||
role: UserRole.Member,
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
const { users } = useStores();
|
const { users } = useStores();
|
||||||
const user = useCurrentUser();
|
const user = useCurrentUser();
|
||||||
@@ -103,7 +93,7 @@ function Invite({ onSubmit }: Props) {
|
|||||||
newInvites.push({
|
newInvites.push({
|
||||||
email: "",
|
email: "",
|
||||||
name: "",
|
name: "",
|
||||||
role: UserRole.Member,
|
role: invites[invites.length - 1].role,
|
||||||
});
|
});
|
||||||
return newInvites;
|
return newInvites;
|
||||||
});
|
});
|
||||||
@@ -122,7 +112,6 @@ function Invite({ onSubmit }: Props) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleCopy = React.useCallback(() => {
|
const handleCopy = React.useCallback(() => {
|
||||||
setLinkCopied(true);
|
|
||||||
toast.success(t("Share link copied"));
|
toast.success(t("Share link copied"));
|
||||||
}, [t]);
|
}, [t]);
|
||||||
|
|
||||||
@@ -137,6 +126,16 @@ function Invite({ onSubmit }: Props) {
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleKeyDown = React.useCallback(
|
||||||
|
(ev: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
if (ev.key === "Enter") {
|
||||||
|
ev.preventDefault();
|
||||||
|
handleAdd();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[handleAdd]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
{team.guestSignin ? (
|
{team.guestSignin ? (
|
||||||
@@ -166,7 +165,7 @@ function Invite({ onSubmit }: Props) {
|
|||||||
)}
|
)}
|
||||||
{team.subdomain && (
|
{team.subdomain && (
|
||||||
<CopyBlock>
|
<CopyBlock>
|
||||||
<Flex align="flex-end">
|
<Flex align="flex-end" gap={8}>
|
||||||
<Input
|
<Input
|
||||||
type="text"
|
type="text"
|
||||||
value={team.url}
|
value={team.url}
|
||||||
@@ -174,73 +173,69 @@ function Invite({ onSubmit }: Props) {
|
|||||||
readOnly
|
readOnly
|
||||||
flex
|
flex
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<CopyToClipboard text={team.url} onCopy={handleCopy}>
|
<CopyToClipboard text={team.url} onCopy={handleCopy}>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
icon={<LinkIcon />}
|
icon={<CopyIcon />}
|
||||||
style={{
|
style={{
|
||||||
marginBottom: "16px",
|
marginBottom: "16px",
|
||||||
}}
|
}}
|
||||||
neutral
|
neutral
|
||||||
>
|
/>
|
||||||
{linkCopied ? t("Link copied") : t("Copy link")}
|
|
||||||
</Button>
|
|
||||||
</CopyToClipboard>
|
</CopyToClipboard>
|
||||||
</Flex>
|
</Flex>
|
||||||
</CopyBlock>
|
</CopyBlock>
|
||||||
)}
|
)}
|
||||||
{invites.map((invite, index) => (
|
<ResizingHeightContainer>
|
||||||
<Flex key={index} gap={8}>
|
{invites.map((invite, index) => (
|
||||||
<Input
|
<Flex key={index} gap={8}>
|
||||||
type="email"
|
<Input
|
||||||
name="email"
|
type="email"
|
||||||
label={t("Email")}
|
name="email"
|
||||||
labelHidden={index !== 0}
|
label={t("Email")}
|
||||||
onChange={(ev) => handleChange(ev, index)}
|
labelHidden={index !== 0}
|
||||||
placeholder={`example@${predictedDomain}`}
|
onKeyDown={handleKeyDown}
|
||||||
value={invite.email}
|
onChange={(ev) => handleChange(ev, index)}
|
||||||
required={index === 0}
|
placeholder={`example@${predictedDomain}`}
|
||||||
autoFocus={index === 0}
|
value={invite.email}
|
||||||
flex
|
required={index === 0}
|
||||||
/>
|
autoFocus
|
||||||
<Input
|
flex
|
||||||
type="text"
|
/>
|
||||||
name="name"
|
<Input
|
||||||
label={t("Full name")}
|
type="text"
|
||||||
labelHidden={index !== 0}
|
name="name"
|
||||||
onChange={(ev) => handleChange(ev, index)}
|
label={t("Name")}
|
||||||
value={invite.name}
|
labelHidden={index !== 0}
|
||||||
required={!!invite.email}
|
onKeyDown={handleKeyDown}
|
||||||
flex
|
onChange={(ev) => handleChange(ev, index)}
|
||||||
/>
|
value={invite.name}
|
||||||
<InputSelectRole
|
required={!!invite.email}
|
||||||
onChange={(role: UserRole) => handleRoleChange(role, index)}
|
flex
|
||||||
value={invite.role}
|
/>
|
||||||
labelHidden={index !== 0}
|
<InputSelectRole
|
||||||
short
|
onChange={(role: UserRole) => handleRoleChange(role, index)}
|
||||||
/>
|
value={invite.role}
|
||||||
{index !== 0 && (
|
labelHidden={index !== 0}
|
||||||
<Remove>
|
short
|
||||||
<Tooltip tooltip={t("Remove invite")} placement="top">
|
/>
|
||||||
<NudeButton onClick={(ev) => handleRemove(ev, index)}>
|
{index !== 0 && (
|
||||||
<CloseIcon />
|
<Remove>
|
||||||
</NudeButton>
|
<Tooltip tooltip={t("Remove invite")} placement="top">
|
||||||
</Tooltip>
|
<NudeButton onClick={(ev) => handleRemove(ev, index)}>
|
||||||
</Remove>
|
<CloseIcon />
|
||||||
)}
|
</NudeButton>
|
||||||
{index === 0 && invites.length > 1 && (
|
</Tooltip>
|
||||||
<Remove>
|
</Remove>
|
||||||
<Spacer />
|
)}
|
||||||
</Remove>
|
</Flex>
|
||||||
)}
|
))}
|
||||||
</Flex>
|
</ResizingHeightContainer>
|
||||||
))}
|
|
||||||
|
|
||||||
<Flex justify="space-between">
|
<Flex justify="space-between">
|
||||||
{invites.length <= UserValidation.maxInvitesPerRequest ? (
|
{invites.length <= UserValidation.maxInvitesPerRequest ? (
|
||||||
<Button type="button" onClick={handleAdd} neutral>
|
<Button type="button" onClick={handleAdd} neutral>
|
||||||
<Trans>Add another</Trans>…
|
{t("Add another")}…
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<span />
|
<span />
|
||||||
@@ -270,12 +265,9 @@ const CopyBlock = styled("div")`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const Remove = styled("div")`
|
const Remove = styled("div")`
|
||||||
|
color: ${s("textTertiary")};
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
`;
|
margin-right: -32px;
|
||||||
|
|
||||||
const Spacer = styled.div`
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default observer(Invite);
|
export default observer(Invite);
|
||||||
|
|||||||
@@ -227,7 +227,7 @@ function Security() {
|
|||||||
value={data.defaultUserRole}
|
value={data.defaultUserRole}
|
||||||
options={[
|
options={[
|
||||||
{
|
{
|
||||||
label: t("Member"),
|
label: t("Editor"),
|
||||||
value: "member",
|
value: "member",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -222,7 +222,7 @@
|
|||||||
"View only": "View only",
|
"View only": "View only",
|
||||||
"No access": "No access",
|
"No access": "No access",
|
||||||
"Role": "Role",
|
"Role": "Role",
|
||||||
"Member": "Member",
|
"Editor": "Editor",
|
||||||
"Viewer": "Viewer",
|
"Viewer": "Viewer",
|
||||||
"Admin": "Admin",
|
"Admin": "Admin",
|
||||||
"{{appName}} is available in your language {{optionLabel}}, would you like to change?": "{{appName}} is available in your language {{optionLabel}}, would you like to change?",
|
"{{appName}} is available in your language {{optionLabel}}, would you like to change?": "{{appName}} is available in your language {{optionLabel}}, would you like to change?",
|
||||||
@@ -637,7 +637,6 @@
|
|||||||
"As an admin you can also <2>enable email sign-in</2>.": "As an admin you can also <2>enable email sign-in</2>.",
|
"As an admin you can also <2>enable email sign-in</2>.": "As an admin you can also <2>enable email sign-in</2>.",
|
||||||
"Want a link to share directly with your team?": "Want a link to share directly with your team?",
|
"Want a link to share directly with your team?": "Want a link to share directly with your team?",
|
||||||
"Email": "Email",
|
"Email": "Email",
|
||||||
"Full name": "Full name",
|
|
||||||
"Remove invite": "Remove invite",
|
"Remove invite": "Remove invite",
|
||||||
"Add another": "Add another",
|
"Add another": "Add another",
|
||||||
"Inviting": "Inviting",
|
"Inviting": "Inviting",
|
||||||
|
|||||||
Reference in New Issue
Block a user