import { observer } from "mobx-react"; import { PlusIcon } from "outline-icons"; import pluralize from "pluralize"; import * as React from "react"; import { useTranslation, Trans } from "react-i18next"; import { Link } from "react-router-dom"; import { toast } from "sonner"; import styled from "styled-components"; import { UserRole } from "@shared/types"; import { parseEmail } from "@shared/utils/email"; import { UserValidation } from "@shared/validations"; import Button from "~/components/Button"; import Flex from "~/components/Flex"; import Input from "~/components/Input"; import InputSelect from "~/components/InputSelect"; import { ResizingHeightContainer } from "~/components/ResizingHeightContainer"; import Text from "~/components/Text"; import Tooltip from "~/components/Tooltip"; import useCurrentTeam from "~/hooks/useCurrentTeam"; import useCurrentUser from "~/hooks/useCurrentUser"; import usePolicy from "~/hooks/usePolicy"; import useStores from "~/hooks/useStores"; type Props = { onSubmit: () => void; }; type InviteRequest = { email: string; name: string; }; function Invite({ onSubmit }: Props) { const [isSaving, setIsSaving] = React.useState(false); const [invites, setInvites] = React.useState([ { email: "", name: "", }, ]); const { users, collections } = useStores(); const user = useCurrentUser(); const team = useCurrentTeam(); const { t } = useTranslation(); const predictedDomain = parseEmail(user.email).domain; const can = usePolicy(team); const [role, setRole] = React.useState(UserRole.Member); const handleSubmit = React.useCallback( async (ev: React.SyntheticEvent) => { ev.preventDefault(); setIsSaving(true); try { const response = await users.invite( invites.filter((i) => i.email).map((memo) => ({ ...memo, role })) ); onSubmit(); if (response.length > 0) { toast.success(t("We sent out your invites!")); } else { toast.message(t("Those email addresses are already invited")); } } catch (err) { toast.error(err.message); } finally { setIsSaving(false); } }, [onSubmit, invites, role, t, users] ); const handleChange = React.useCallback((ev, index) => { setInvites((prevInvites) => { const newInvites = [...prevInvites]; newInvites[index][ev.target.name] = ev.target.value; return newInvites; }); }, []); const handleAdd = React.useCallback(() => { if (invites.length >= UserValidation.maxInvitesPerRequest) { toast.message( t("Sorry, you can only send {{MAX_INVITES}} invites at a time", { MAX_INVITES: UserValidation.maxInvitesPerRequest, }) ); } setInvites((prevInvites) => { const newInvites = [...prevInvites]; newInvites.push({ email: "", name: "", }); return newInvites; }); }, [invites, t]); const handleKeyDown = React.useCallback( (ev: React.KeyboardEvent) => { if (ev.key === "Enter") { ev.preventDefault(); handleAdd(); } }, [handleAdd] ); const roleName = pluralize(role); const collectionCount = collections.nonPrivate.length; const collectionAccessNote = collectionCount ? ( Invited {{ roleName }} will receive access to{" "} {collections.nonPrivate.map((collection) => (
  • {collection.name}
  • ))} } > {{ collectionCount }} collections
    .
    ) : undefined; const options = React.useMemo(() => { const memo = []; if (user.isAdmin) { memo.push({ label: t("Admin"), description: t("Can manage all workspace settings"), value: UserRole.Admin, }); } return [ ...memo, { label: t("Editor"), description: t("Can create, edit, and delete documents"), value: UserRole.Member, }, { label: t("Viewer"), description: t("Can view and comment"), value: UserRole.Viewer, }, ]; }, [t, user]); return (
    {team.guestSignin ? ( {" "} {collectionAccessNote} ) : ( {" "} {collectionAccessNote} {can.update && ( As an admin you can also{" "} enable email sign-in. )} )} setRole(r as UserRole)} value={role} /> {invites.map((invite, index) => ( handleChange(ev, index)} placeholder={`name@${predictedDomain}`} value={invite.email} required={index === 0} autoFocus flex /> handleChange(ev, index)} value={invite.name} required={!!invite.email} flex /> ))} {invites.length <= UserValidation.maxInvitesPerRequest ? ( ) : null}
    ); } const StyledInput = styled(Input)` margin-bottom: -4px; min-width: 0; flex-shrink: 1; `; export default observer(Invite);