Validate for invited members

This commit is contained in:
Joe Au-Yeung 2022-10-27 14:52:59 -04:00
parent 20488b35c8
commit d34b4ac931
3 changed files with 129 additions and 79 deletions

View File

@ -1,7 +1,8 @@
import Head from "next/head";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import { useFieldArray, useForm } from "react-hook-form";
import { useForm } from "react-hook-form";
import { Toaster } from "react-hot-toast";
import { z } from "zod";
// import TeamGeneralSettings from "@calcom/features/teams/createNewTeam/TeamGeneralSettings";
@ -36,20 +37,9 @@ const CreateNewTeamPage = () => {
const router = useRouter();
const { t } = useLocale();
const [teamId, setTeamId] = useState<number>();
const formMethods = useForm<NewTeamFormValues>();
// const { data: user, isLoading } = trpc.useQuery(["viewer.me"], {
// onSuccess: () => {
// if (user) {
// formMethods.setValue("members", [
// { name: user.name, emailOrUsername: user.username, role: "OWNER", avatar: user.avatar },
// ]);
// }
// },
// });
useEffect(() => {
console.log(formMethods.getValues());
}, [formMethods]);
@ -93,6 +83,9 @@ const CreateNewTeamPage = () => {
<title>Create a new Team</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div>
<Toaster position="bottom-right" />
</div>
<div className="mx-auto px-4 py-24">
<div className="relative">
<div className="sm:mx-auto sm:w-full sm:max-w-[600px]">
@ -119,7 +112,6 @@ const CreateNewTeamPage = () => {
nextStep={() => {
goToIndex(1);
}}
setTeamId={(teamId: number) => setTeamId(teamId)}
/>
)}

View File

@ -1,6 +1,6 @@
import { MembershipRole } from "@prisma/client";
import React, { useState, SyntheticEvent, useMemo } from "react";
import { useForm, Controller } from "react-hook-form";
import { useForm, Controller, useFormContext } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { TeamWithMembers } from "@calcom/lib/server/queries/teams";
@ -40,10 +40,24 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
];
}, [t]);
const formMethods = useFormContext<NewTeamFormValues>();
const newMemberFormMethods = useForm<NewMemberForm>();
const validateUniqueInvite = (value: string) => {
const members = formMethods.getValues("members");
return !(
members.some((member) => member?.username === value) ||
members.some((member) => member?.email === value)
);
};
return (
<Dialog open={props.isOpen} onOpenChange={props.onExit}>
<Dialog
open={props.isOpen}
onOpenChange={() => {
props.onExit();
newMemberFormMethods.reset();
}}>
<DialogContent
type="creation"
useOwnActionButtons
@ -59,16 +73,22 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
<Controller
name="emailOrUsername"
control={newMemberFormMethods.control}
rules={{ required: true, minLength: 1 }}
render={({ field: { onChange } }) => (
<TextField
label={t("email_or_username")}
id="inviteUser"
name="inviteUser"
placeholder="email@example.com"
required
onChange={onChange}
/>
rules={{
required: "Enter a username or a password",
validate: (value) => validateUniqueInvite(value) || "Member already invited",
}}
render={({ field: { onChange }, fieldState: { error } }) => (
<>
<TextField
label={t("email_or_username")}
id="inviteUser"
name="inviteUser"
placeholder="email@example.com"
required
onChange={onChange}
/>
{error && <span className="text-sm text-red-800">{error.message}</span>}
</>
)}
/>
<Controller
@ -108,7 +128,13 @@ export default function MemberInvitationModal(props: MemberInvitationModalProps)
/>
</div>
<DialogFooter>
<Button type="button" color="secondary" onClick={props.onExit}>
<Button
type="button"
color="secondary"
onClick={() => {
props.onExit();
newMemberFormMethods.reset();
}}>
{t("cancel")}
</Button>
<Button

View File

@ -1,3 +1,4 @@
import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useState, useEffect } from "react";
import { useFormContext, Controller, useFieldArray } from "react-hook-form";
@ -18,6 +19,7 @@ const AddNewTeamMembers = () => {
const { t } = useLocale();
const utils = trpc.useContext();
const router = useRouter();
const session = useSession();
const [memberInviteModal, setMemberInviteModal] = useState(false);
const [inviteMemberInput, setInviteMemberInput] = useState<NewMemberForm>({
@ -41,10 +43,25 @@ const AddNewTeamMembers = () => {
membersFieldArray.append(newMember);
setSkeletonMember(false);
},
onError: (error) => {
showToast(error.message, "error");
setSkeletonMember(false);
},
});
useEffect(() => {
if (inviteMemberInput) {
if (session.status !== "loading" && !formMethods.getValues("members").length) {
membersFieldArray.append({
name: session.data.user.name,
email: session.data.user.email,
username: session.data.user.username,
role: "OWNER",
});
}
}, [session]);
useEffect(() => {
if (inviteMemberInput.emailOrUsername) {
refetch();
}
}, [inviteMemberInput]);
@ -67,7 +84,15 @@ const AddNewTeamMembers = () => {
// });
const handleInviteTeamMember = (values: NewMemberForm) => {
console.log(values);
// const members = formMethods.getValues("members");
// if (
// members.some((member) => member.username === values.emailOrUsername) ||
// members.some((member) => member.email === values.emailOrUsername)
// ) {
// showToast("Member has already been added", "error");
// setMemberInviteModal(false);
// return;
// }
setInviteMemberInput(values);
setMemberInviteModal(false);
setSkeletonMember(true);
@ -78,67 +103,59 @@ const AddNewTeamMembers = () => {
membersFieldArray.remove(memberIndex);
};
// if (isLoading) return <AddNewTeamMemberSkeleton />;
if (session.status === "loading") return <AddNewTeamMemberSkeleton />;
return (
<>
<Controller
name="members"
defaultValue={[
{
name: user?.name || "",
username: user?.username || "",
email: user?.email || "",
role: "OWNER",
avatar: user?.avatar || "",
},
]}
render={({ field: { value } }) => (
<>
<div>
<ul className="rounded-md border">
{value.map((member: PendingMember, index: number) => (
<li
key={member.email}
className={classNames(
"flex items-center justify-between p-6 text-sm",
index !== 0 && "border-t"
)}>
<div className="flex space-x-2">
<Avatar
gravatarFallbackMd5="teamMember"
size="mdLg"
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
alt="owner-avatar"
/>
<div>
<div className="flex space-x-1">
<p>{member?.name || member?.email || t("team_member")}</p>
{/* Assume that the first member of the team is the creator */}
{index === 0 && <Badge variant="green">{t("you")}</Badge>}
{member.role !== "OWNER" && <Badge variant="orange">{t("pending")}</Badge>}
{member.role === "MEMBER" && <Badge variant="gray">{t("member")}</Badge>}
{member.role === "ADMIN" && <Badge variant="default">{t("admin")}</Badge>}
{member.sendInviteEmail && <Badge variant="blue">{t("send_email")}</Badge>}
{value &&
value.map((member: PendingMember, index: number) => (
<li
key={member.email}
className={classNames(
"flex items-center justify-between p-6 text-sm",
index !== 0 && "border-t"
)}>
<div className="flex space-x-2">
<Avatar
gravatarFallbackMd5="teamMember"
size="mdLg"
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
alt="owner-avatar"
/>
<div>
<div className="flex space-x-1">
<p>{member?.name || member?.email || t("team_member")}</p>
{/* Assume that the first member of the team is the creator */}
{index === 0 && <Badge variant="green">{t("you")}</Badge>}
{member.role !== "OWNER" && <Badge variant="orange">{t("pending")}</Badge>}
{member.role === "MEMBER" && <Badge variant="gray">{t("member")}</Badge>}
{member.role === "ADMIN" && <Badge variant="default">{t("admin")}</Badge>}
{member.sendInviteEmail && <Badge variant="blue">{t("send_email")}</Badge>}
</div>
{member.username ? (
<p className="text-gray-600">{`${WEBAPP_URL}/${member?.username}`}</p>
) : (
<p className="text-gray-600">{t("not_on_cal")}</p>
)}
</div>
{member.username ? (
<p className="text-gray-600">{`${WEBAPP_URL}/${member?.username}`}</p>
) : (
<p className="text-gray-600">{t("not_on_cal")}</p>
)}
</div>
</div>
{member.role !== "OWNER" && (
<Button
StartIcon={Icon.FiTrash2}
size="icon"
color="secondary"
className="h-[36px] w-[36px]"
onClick={() => handleDeleteMember(member.email)}
/>
)}
</li>
))}
{member.role !== "OWNER" && (
<Button
StartIcon={Icon.FiTrash2}
size="icon"
color="secondary"
className="h-[36px] w-[36px]"
onClick={() => handleDeleteMember(member.email)}
/>
)}
</li>
))}
{skeletonMember && <SkeletonMember />}
</ul>
@ -151,12 +168,27 @@ const AddNewTeamMembers = () => {
{t("add_team_member")}
</Button>
</div>
<MemberInvitationModal
isOpen={memberInviteModal}
onExit={() => setMemberInviteModal(false)}
onSubmit={handleInviteTeamMember}
/>
<hr className="my-6 border-neutral-200" />
<Button
EndIcon={Icon.FiArrowRight}
className="mt-6 w-full justify-center"
// onClick={() => {
// if (team) {
// teamCheckoutMutation.mutate({ teamId, seats: team.members.length });
// } else {
// showToast(t("error_creating_team"), "error");
// }
// }
// }
>
{t("checkout")}
</Button>
</>
)}
/>