fix: bulk member invite and table layout (#5209)

This commit is contained in:
Johannes
2025-04-02 02:32:01 -07:00
committed by GitHub
parent dfb6c4cd9e
commit aa55cec060
5 changed files with 79 additions and 74 deletions

View File

@@ -26,12 +26,15 @@ export const EditMemberships = async ({
return (
<div>
<div className="rounded-lg border border-slate-200">
<div className="grid h-12 grid-cols-5 content-center rounded-t-lg bg-slate-100 px-4 text-left text-sm font-semibold text-slate-900">
<div className="col-span-1">{t("common.full_name")}</div>
<div className="col-span-1 text-center">{t("common.email")}</div>
{canDoRoleManagement && <div className="col-span-1 text-center">{t("common.role")}</div>}
<div className="col-span-1 text-center">{t("common.status")}</div>
<div className="col-span-1"></div>
<div className="flex h-12 w-full max-w-full items-center gap-x-4 rounded-t-lg bg-slate-100 px-4 text-left text-sm font-semibold text-slate-900">
<div className="w-1/2 overflow-hidden">{t("common.full_name")}</div>
<div className="w-1/2 overflow-hidden">{t("common.email")}</div>
{canDoRoleManagement && <div className="min-w-[100px] whitespace-nowrap">{t("common.role")}</div>}
<div className="min-w-[80px] whitespace-nowrap">{t("common.status")}</div>
<div className="min-w-[125px] whitespace-nowrap">{t("common.actions")}</div>
</div>
{role && (

View File

@@ -110,47 +110,46 @@ export const MemberActions = ({ organization, member, invite, showDeleteButton }
return (
<div className="flex gap-2">
{showDeleteButton && (
<>
<TooltipRenderer tooltipContent={t("common.delete")}>
<Button
variant="secondary"
size="icon"
id="deleteMemberButton"
onClick={() => setDeleteMemberModalOpen(true)}>
<TrashIcon />
</Button>
</TooltipRenderer>
</>
)}
<TooltipRenderer tooltipContent={t("common.delete")} shouldRender={!!showDeleteButton}>
<Button
variant="destructive"
size="icon"
id="deleteMemberButton"
disabled={!showDeleteButton}
onClick={() => setDeleteMemberModalOpen(true)}>
<TrashIcon />
</Button>
</TooltipRenderer>
{invite && (
<>
<TooltipRenderer tooltipContent={t("environments.settings.general.share_invite_link")}>
<Button
variant="secondary"
size="icon"
id="shareInviteButton"
onClick={() => {
handleShareInvite();
}}>
<ShareIcon />
</Button>
</TooltipRenderer>
<TooltipRenderer
tooltipContent={t("environments.settings.general.share_invite_link")}
shouldRender={!!invite}>
<Button
variant="secondary"
size="icon"
id="shareInviteButton"
disabled={!invite}
onClick={() => {
handleShareInvite();
}}>
<ShareIcon />
</Button>
</TooltipRenderer>
<TooltipRenderer tooltipContent={t("environments.settings.general.resend_invitation_email")}>
<Button
variant="secondary"
size="icon"
id="resendInviteButton"
onClick={() => {
handleResendInvite();
}}>
<SendHorizonalIcon />
</Button>
</TooltipRenderer>
</>
)}
<TooltipRenderer
tooltipContent={t("environments.settings.general.resend_invitation_email")}
shouldRender={!!invite}>
<Button
variant="secondary"
size="icon"
id="resendInviteButton"
disabled={!invite}
onClick={() => {
handleResendInvite();
}}>
<SendHorizonalIcon />
</Button>
</TooltipRenderer>
<DeleteDialog
open={isDeleteMemberModalOpen}

View File

@@ -86,20 +86,21 @@ export const MembersInfo = ({
};
return (
<div className="grid-cols-5" id="membersInfoWrapper">
<div className="max-w-full space-y-4 px-4 py-3" id="membersInfoWrapper">
{allMembers.map((member) => (
<div
className="singleMemberInfo grid h-auto w-full grid-cols-5 content-center rounded-lg px-4 py-3 text-left text-sm text-slate-900"
id="singleMemberInfo"
className="flex w-full max-w-full items-center gap-x-4 text-left text-sm text-slate-900"
key={member.email}>
<div className="ph-no-capture col-span-1 flex flex-col justify-center break-all">
<p>{member.name}</p>
<div className="ph-no-capture w-1/2 overflow-hidden">
<p className="w-full truncate">{member.name}</p>
</div>
<div className="ph-no-capture col-span-1 flex flex-col justify-center break-all text-center">
{member.email}
<div className="ph-no-capture w-1/2 overflow-hidden">
<p className="w-full truncate"> {member.email}</p>
</div>
<div className="ph-no-capture col-span-1 flex flex-col items-center justify-center break-all">
{canDoRoleManagement && allMembers?.length > 0 && (
{canDoRoleManagement && allMembers?.length > 0 && (
<div className="ph-no-capture min-w-[100px]">
<EditMembershipRole
currentUserRole={currentUserRole}
memberRole={member.role}
@@ -111,17 +112,16 @@ export const MembersInfo = ({
doesOrgHaveMoreThanOneOwner={doesOrgHaveMoreThanOneOwner}
isFormbricksCloud={isFormbricksCloud}
/>
)}
</div>
<div className="col-span-1 flex items-center justify-center">{getMembershipBadge(member)}</div>
<div className="col-span-1 flex items-center justify-end gap-x-4 pr-4">
<MemberActions
organization={organization}
member={!isInvitee(member) ? member : undefined}
invite={isInvitee(member) ? member : undefined}
showDeleteButton={showDeleteButton(member)}
/>
</div>
</div>
)}
<div className="min-w-[80px]">{getMembershipBadge(member)}</div>
<MemberActions
organization={organization}
member={!isInvitee(member) ? member : undefined}
invite={isInvitee(member) ? member : undefined}
showDeleteButton={showDeleteButton(member)}
/>
</div>
))}
</div>

View File

@@ -39,19 +39,23 @@ export const BulkInviteTab = ({
}
Papa.parse(csvFile, {
skipEmptyLines: true,
comments: "Full Name,Email Address,Role",
complete: (results: ParseResult<string[]>) => {
header: true,
transformHeader: (header) => {
if (header === "Full Name") return "name";
if (header === "Email Address") return "email";
if (header === "Role") return "role";
return header;
},
complete: (results: ParseResult<{ name: string; email: string; role: string }>) => {
const members = results.data.map((csv) => {
const [name, email, role] = csv;
let orgRole = canDoRoleManagement ? role.trim().toLowerCase() : "owner";
let orgRole = canDoRoleManagement ? csv.role.trim().toLowerCase() : "owner";
if (!isFormbricksCloud) {
orgRole = orgRole === "billing" ? "owner" : orgRole;
}
return {
name: name.trim(),
email: email.trim(),
name: csv.name.trim(),
email: csv.email.trim(),
role: orgRole as TOrganizationRole,
teamIds: [],
};
@@ -60,7 +64,6 @@ export const BulkInviteTab = ({
ZInvitees.parse(members);
onSubmit(members);
} catch (err) {
console.error(err.message);
toast.error(t("environments.settings.general.please_check_csv_file"));
}
setOpen(false);

View File

@@ -48,7 +48,7 @@ test.describe("Invite, accept and remove organization member", async () => {
await test.step("Copy invite Link", async () => {
await expect(page.locator("#membersInfoWrapper")).toBeVisible();
const lastMemberInfo = page.locator("#membersInfoWrapper > .singleMemberInfo:last-child");
const lastMemberInfo = page.locator("#membersInfoWrapper > #singleMemberInfo:last-child");
await expect(lastMemberInfo).toBeVisible();
const pendingSpan = lastMemberInfo.locator("span").locator("span").filter({ hasText: "Pending" });
@@ -106,7 +106,7 @@ test.describe("Invite, accept and remove organization member", async () => {
// await expect(page.locator("#membersInfoWrapper")).toBeVisible();
// const lastMemberInfo = page.locator("#membersInfoWrapper > .singleMemberInfo:last-child");
// const lastMemberInfo = page.locator("#membersInfoWrapper > #singleMemberInfo:last-child");
// await expect(lastMemberInfo).toBeVisible();
// const deleteMemberButton = lastMemberInfo.locator("#deleteMemberButton");