* Allow user invitations * Restyle inviting members * Remove annoying comment * Add page for accepting an invitation * Send invitation email with Brevo * Prevent admins from removing personal project users * Mark access ceontrol for cancelProjectInvitation * Make RadioGroup controlled * Shorten form helper text * Use nodemailer to send emails * Update .env.example
129 lines
3.6 KiB
TypeScript
129 lines
3.6 KiB
TypeScript
import {
|
|
Button,
|
|
FormControl,
|
|
FormLabel,
|
|
Input,
|
|
FormHelperText,
|
|
HStack,
|
|
Modal,
|
|
ModalBody,
|
|
ModalCloseButton,
|
|
ModalContent,
|
|
ModalFooter,
|
|
ModalHeader,
|
|
ModalOverlay,
|
|
Spinner,
|
|
Text,
|
|
VStack,
|
|
RadioGroup,
|
|
Radio,
|
|
} from "@chakra-ui/react";
|
|
import { useState, useEffect } from "react";
|
|
|
|
import { api } from "~/utils/api";
|
|
import { useHandledAsyncCallback, useSelectedProject } from "~/utils/hooks";
|
|
import { maybeReportError } from "~/utils/errorHandling/maybeReportError";
|
|
import { type ProjectUserRole } from "@prisma/client";
|
|
|
|
export const InviteMemberModal = ({
|
|
isOpen,
|
|
onClose,
|
|
}: {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}) => {
|
|
const selectedProject = useSelectedProject().data;
|
|
const utils = api.useContext();
|
|
|
|
const [email, setEmail] = useState("");
|
|
const [role, setRole] = useState<ProjectUserRole>("MEMBER");
|
|
|
|
useEffect(() => {
|
|
setEmail("");
|
|
setRole("MEMBER");
|
|
}, [isOpen]);
|
|
|
|
const emailIsValid = !email || !email.match(/.+@.+\..+/);
|
|
|
|
const inviteMemberMutation = api.users.inviteToProject.useMutation();
|
|
|
|
const [inviteMember, isInviting] = useHandledAsyncCallback(async () => {
|
|
if (!selectedProject?.id || !role) return;
|
|
const resp = await inviteMemberMutation.mutateAsync({
|
|
projectId: selectedProject.id,
|
|
email,
|
|
role,
|
|
});
|
|
if (maybeReportError(resp)) return;
|
|
await utils.projects.get.invalidate();
|
|
onClose();
|
|
}, [inviteMemberMutation, email, role, selectedProject?.id, onClose]);
|
|
|
|
return (
|
|
<Modal isOpen={isOpen} onClose={onClose}>
|
|
<ModalOverlay />
|
|
<ModalContent w={1200}>
|
|
<ModalHeader>
|
|
<HStack>
|
|
<Text>Invite Member</Text>
|
|
</HStack>
|
|
</ModalHeader>
|
|
<ModalCloseButton />
|
|
<ModalBody>
|
|
<VStack spacing={8} alignItems="flex-start">
|
|
<Text>
|
|
Invite a new member to <b>{selectedProject?.name}</b>.
|
|
</Text>
|
|
|
|
<RadioGroup
|
|
value={role}
|
|
onChange={(e) => setRole(e as ProjectUserRole)}
|
|
colorScheme="orange"
|
|
>
|
|
<VStack w="full" alignItems="flex-start">
|
|
<Radio value="MEMBER">
|
|
<Text fontSize="sm">MEMBER</Text>
|
|
</Radio>
|
|
<Radio value="ADMIN">
|
|
<Text fontSize="sm">ADMIN</Text>
|
|
</Radio>
|
|
</VStack>
|
|
</RadioGroup>
|
|
<FormControl>
|
|
<FormLabel>Email</FormLabel>
|
|
<Input
|
|
type="email"
|
|
value={email}
|
|
onChange={(e) => setEmail(e.target.value)}
|
|
onKeyDown={(e) => {
|
|
if (e.key === "Enter" && (e.metaKey || e.ctrlKey || e.shiftKey)) {
|
|
e.preventDefault();
|
|
e.currentTarget.blur();
|
|
inviteMember();
|
|
}
|
|
}}
|
|
/>
|
|
<FormHelperText>Enter the email of the person you want to invite.</FormHelperText>
|
|
</FormControl>
|
|
</VStack>
|
|
</ModalBody>
|
|
<ModalFooter mt={4}>
|
|
<HStack>
|
|
<Button colorScheme="gray" onClick={onClose} minW={24}>
|
|
<Text>Cancel</Text>
|
|
</Button>
|
|
<Button
|
|
colorScheme="orange"
|
|
onClick={inviteMember}
|
|
minW={24}
|
|
isDisabled={emailIsValid || isInviting}
|
|
>
|
|
{isInviting ? <Spinner boxSize={4} /> : <Text>Send Invitation</Text>}
|
|
</Button>
|
|
</HStack>
|
|
</ModalFooter>
|
|
</ModalContent>
|
|
</Modal>
|
|
);
|
|
};
|