fix: move organization members list (#17531)
Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import MembersPage from "~/members/members-view";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
await _generateMetadata(
|
||||
(t) => t("organization_members"),
|
||||
(t) => t("organization_description")
|
||||
);
|
||||
|
||||
export default WithLayout({
|
||||
Page: MembersPage,
|
||||
});
|
||||
@@ -2,7 +2,7 @@ import { _generateMetadata, getFixedT } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSessionForAppDir } from "@calcom/features/auth/lib/get-server-session-for-app-dir";
|
||||
import LegacyPage from "@calcom/features/ee/organizations/pages/settings/members";
|
||||
import LegacyPage from "@calcom/features/ee/organizations/pages/members";
|
||||
import SettingsHeader from "@calcom/features/settings/appDir/SettingsHeader";
|
||||
import SettingsLayoutAppDir from "@calcom/features/settings/appDir/SettingsLayoutAppDir";
|
||||
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
"use client";
|
||||
|
||||
import MembersView from "@calcom/features/ee/organizations/pages/members";
|
||||
import Shell from "@calcom/features/shell/Shell";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { HeadSeo } from "@calcom/ui";
|
||||
|
||||
const MembersPage: React.FC = () => {
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<Shell
|
||||
withoutMain={false}
|
||||
title={t("organization_members")}
|
||||
description={t("organization_description")}
|
||||
withoutSeo
|
||||
heading={t("organization_members")}
|
||||
hideHeadingOnMobile
|
||||
subtitle={t("organization_description")}>
|
||||
<HeadSeo title={t("organization_members")} description={t("organization_description")} />
|
||||
|
||||
<MembersView />
|
||||
</Shell>
|
||||
);
|
||||
};
|
||||
|
||||
export default MembersPage;
|
||||
@@ -529,6 +529,7 @@ test.describe("Bookings", () => {
|
||||
const { invitedUserEmail } = await inviteExistingUserToOrganization({
|
||||
page,
|
||||
organizationId: org.id,
|
||||
organizationSlug: org.slug,
|
||||
user: userOutsideOrganization,
|
||||
usersFixture: users,
|
||||
});
|
||||
|
||||
@@ -6,15 +6,17 @@ import { submitAndWaitForResponse } from "../../lib/testUtils";
|
||||
export const inviteUserToOrganization = async ({
|
||||
page,
|
||||
organizationId,
|
||||
organizationSlug,
|
||||
email,
|
||||
usersFixture,
|
||||
}: {
|
||||
page: Page;
|
||||
organizationId: number;
|
||||
organizationSlug: string | null;
|
||||
email: string;
|
||||
usersFixture: ReturnType<typeof createUsersFixture>;
|
||||
}) => {
|
||||
await page.goto("/settings/organizations/members");
|
||||
await page.goto(`/settings/organizations/${organizationSlug}/members`);
|
||||
const invitedUserEmail = usersFixture.trackEmail({
|
||||
username: email.split("@")[0],
|
||||
domain: email.split("@")[1],
|
||||
@@ -26,17 +28,19 @@ export const inviteUserToOrganization = async ({
|
||||
export const inviteExistingUserToOrganization = async ({
|
||||
page,
|
||||
organizationId,
|
||||
organizationSlug,
|
||||
user,
|
||||
usersFixture,
|
||||
}: {
|
||||
page: Page;
|
||||
organizationId: number;
|
||||
organizationSlug: string | null;
|
||||
user: {
|
||||
email: string;
|
||||
};
|
||||
usersFixture: ReturnType<typeof createUsersFixture>;
|
||||
}) => {
|
||||
await page.goto("/settings/organizations/members");
|
||||
await page.goto(`/settings/organizations/${organizationSlug}/members`);
|
||||
await inviteAnEmail(page, user.email);
|
||||
return { invitedUserEmail: user.email };
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ test.describe("Organization", () => {
|
||||
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true });
|
||||
const { team: org } = await orgOwner.getOrgMembership();
|
||||
await orgOwner.apiLogin();
|
||||
await page.goto("/settings/organizations/members");
|
||||
await page.goto(`/settings/organizations/${org.slug}/members`);
|
||||
|
||||
await test.step("By email", async () => {
|
||||
const invitedUserEmail = users.trackEmail({ username: "rick", domain: "domain.com" });
|
||||
@@ -42,6 +42,7 @@ test.describe("Organization", () => {
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: false,
|
||||
@@ -61,6 +62,7 @@ test.describe("Organization", () => {
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
@@ -78,6 +80,7 @@ test.describe("Organization", () => {
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
@@ -119,6 +122,7 @@ test.describe("Organization", () => {
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: false,
|
||||
@@ -156,6 +160,7 @@ test.describe("Organization", () => {
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
@@ -184,6 +189,7 @@ test.describe("Organization", () => {
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
@@ -203,7 +209,7 @@ test.describe("Organization", () => {
|
||||
});
|
||||
const { team: org } = await orgOwner.getOrgMembership();
|
||||
await orgOwner.apiLogin();
|
||||
await page.goto("/settings/organizations/members");
|
||||
await page.goto(`/settings/organizations/${org.slug}/members`);
|
||||
|
||||
await test.step("By email", async () => {
|
||||
const invitedUserEmail = users.trackEmail({ username: "rick", domain: "example.com" });
|
||||
@@ -219,6 +225,7 @@ test.describe("Organization", () => {
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
@@ -238,6 +245,7 @@ test.describe("Organization", () => {
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
@@ -255,6 +263,7 @@ test.describe("Organization", () => {
|
||||
expect(dbUser?.username).toBe(usernameDerivedFromEmail);
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
@@ -346,6 +355,7 @@ test.describe("Organization", () => {
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
@@ -382,6 +392,7 @@ test.describe("Organization", () => {
|
||||
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
@@ -411,6 +422,7 @@ test.describe("Organization", () => {
|
||||
});
|
||||
await expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug: org.slug,
|
||||
username: usernameDerivedFromEmail,
|
||||
role: "member",
|
||||
isMemberShipAccepted: true,
|
||||
@@ -542,19 +554,21 @@ async function inviteAnEmail(page: Page, invitedUserEmail: string, teamPage?: bo
|
||||
|
||||
async function expectUserToBeAMemberOfOrganization({
|
||||
page,
|
||||
orgSlug,
|
||||
username,
|
||||
email,
|
||||
role,
|
||||
isMemberShipAccepted,
|
||||
}: {
|
||||
page: Page;
|
||||
orgSlug: string | null;
|
||||
username: string;
|
||||
role: string;
|
||||
isMemberShipAccepted: boolean;
|
||||
email: string;
|
||||
}) {
|
||||
// Check newly invited member is not pending anymore
|
||||
await page.goto("/settings/organizations/members");
|
||||
await page.goto(`/settings/organizations/${orgSlug}/members`);
|
||||
await expect(page.locator(`[data-testid="member-${username}-username"]`)).toHaveText(username);
|
||||
await expect(page.locator(`[data-testid="member-${username}-email"]`)).toHaveText(email);
|
||||
expect((await page.locator(`[data-testid="member-${username}-role"]`).textContent())?.toLowerCase()).toBe(
|
||||
|
||||
@@ -46,7 +46,7 @@ test.describe("Organization - Privacy", () => {
|
||||
});
|
||||
|
||||
await owner.apiLogin();
|
||||
await page.goto("/settings/organizations/members");
|
||||
await page.goto(`/settings/organizations/${org.slug}/members`);
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
|
||||
const tableLocator = await page.getByTestId("user-list-data-table");
|
||||
@@ -54,7 +54,7 @@ test.describe("Organization - Privacy", () => {
|
||||
await expect(tableLocator).toBeVisible();
|
||||
|
||||
await memberInOrg.apiLogin();
|
||||
await page.goto("/settings/organizations/members");
|
||||
await page.goto(`/settings/organizations/${org.slug}/members`);
|
||||
await page.waitForLoadState("domcontentloaded");
|
||||
const userDataTable = await page.getByTestId("user-list-data-table");
|
||||
const membersPrivacyWarning = await page.getByTestId("members-privacy-warning");
|
||||
|
||||
@@ -17,11 +17,7 @@ const PoweredByCal = ({ logoOnly }: { logoOnly?: boolean }) => {
|
||||
{!logoOnly && <>{t("powered_by")} </>}
|
||||
{APP_NAME === "Cal.com" || !hasValidLicense ? (
|
||||
<>
|
||||
<img
|
||||
className="-mt-px inline h-[10px] w-auto dark:invert"
|
||||
src="/api/logo"
|
||||
alt="Cal.com Logo"
|
||||
/>
|
||||
<img className="-mt-px inline h-[10px] w-auto dark:invert" src="/api/logo" alt="Cal.com Logo" />
|
||||
</>
|
||||
) : (
|
||||
<span className="text-emphasis font-semibold opacity-50 hover:opacity-100">{APP_NAME}</span>
|
||||
|
||||
-1
@@ -5,7 +5,6 @@ import { UserListTable } from "@calcom/features/users/components/UserTable/UserL
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import { Meta } from "@calcom/ui";
|
||||
|
||||
const MembersView = () => {
|
||||
const { t } = useLocale();
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
Button,
|
||||
Form,
|
||||
Label,
|
||||
Meta,
|
||||
Select,
|
||||
showToast,
|
||||
SkeletonButton,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { Meta } from "@calcom/ui";
|
||||
|
||||
import { OtherTeamsListing } from "./../components/OtherTeamsListing";
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
ImageUploader,
|
||||
Label,
|
||||
LinkIconButton,
|
||||
Meta,
|
||||
showToast,
|
||||
SkeletonContainer,
|
||||
SkeletonText,
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
ImageUploader,
|
||||
Label,
|
||||
LinkIconButton,
|
||||
Meta,
|
||||
showToast,
|
||||
SkeletonAvatar,
|
||||
SkeletonButton,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useSession } from "next-auth/react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { AppSkeletonLoader as SkeletonLoader, Meta } from "@calcom/ui";
|
||||
import { AppSkeletonLoader as SkeletonLoader } from "@calcom/ui";
|
||||
|
||||
import SSOConfiguration from "../components/SSOConfiguration";
|
||||
|
||||
|
||||
@@ -78,10 +78,6 @@ const tabs: VerticalTabItemProps[] = [
|
||||
name: "general",
|
||||
href: "/settings/organizations/general",
|
||||
},
|
||||
{
|
||||
name: "members",
|
||||
href: "/settings/organizations/members",
|
||||
},
|
||||
{
|
||||
name: "privacy",
|
||||
href: "/settings/organizations/privacy",
|
||||
|
||||
@@ -77,10 +77,6 @@ const tabs: VerticalTabItemProps[] = [
|
||||
name: "general",
|
||||
href: "/settings/organizations/general",
|
||||
},
|
||||
{
|
||||
name: "members",
|
||||
href: "/settings/organizations/members",
|
||||
},
|
||||
{
|
||||
name: "privacy",
|
||||
href: "/settings/organizations/privacy",
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { useSession } from "next-auth/react";
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { useIsEmbed } from "@calcom/embed-core/embed-iframe";
|
||||
import UnconfirmedBookingBadge from "@calcom/features/bookings/UnconfirmedBookingBadge";
|
||||
import {
|
||||
useOrgBranding,
|
||||
type OrganizationBranding,
|
||||
} from "@calcom/features/ee/organizations/context/provider";
|
||||
import { KBarTrigger } from "@calcom/features/kbar/Kbar";
|
||||
import { classNames } from "@calcom/lib";
|
||||
|
||||
@@ -11,7 +16,7 @@ import { NavigationItem, MobileNavigationItem, MobileNavigationMoreItem } from "
|
||||
|
||||
export const MORE_SEPARATOR_NAME = "more";
|
||||
|
||||
const navigation: NavigationItemType[] = [
|
||||
const getNavigationItems = (orgBranding: OrganizationBranding): NavigationItemType[] => [
|
||||
{
|
||||
name: "event_types_page_title",
|
||||
href: "/event-types",
|
||||
@@ -29,6 +34,15 @@ const navigation: NavigationItemType[] = [
|
||||
href: "/availability",
|
||||
icon: "clock",
|
||||
},
|
||||
...(orgBranding
|
||||
? [
|
||||
{
|
||||
name: "members",
|
||||
href: `/settings/organizations/${orgBranding.slug}/members`,
|
||||
icon: "building",
|
||||
} satisfies NavigationItemType,
|
||||
]
|
||||
: []),
|
||||
{
|
||||
name: "teams",
|
||||
href: "/teams",
|
||||
@@ -102,7 +116,7 @@ const navigation: NavigationItemType[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const platformNavigation: NavigationItemType[] = [
|
||||
const platformNavigationItems: NavigationItemType[] = [
|
||||
{
|
||||
name: "Dashboard",
|
||||
href: "/settings/platform/",
|
||||
@@ -144,33 +158,36 @@ const platformNavigation: NavigationItemType[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const getDesktopNavigationItems = (isPlatformNavigation = false) => {
|
||||
const navigationType = !isPlatformNavigation ? navigation : platformNavigation;
|
||||
const moreSeparatorIndex = navigationType.findIndex((item) => item.name === MORE_SEPARATOR_NAME);
|
||||
const useNavigationItems = (isPlatformNavigation = false) => {
|
||||
const orgBranding = useOrgBranding();
|
||||
return useMemo(() => {
|
||||
const navigationType = !isPlatformNavigation ? getNavigationItems(orgBranding) : platformNavigationItems;
|
||||
const moreSeparatorIndex = navigationType.findIndex((item) => item.name === MORE_SEPARATOR_NAME);
|
||||
|
||||
const { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMoreItems } = (
|
||||
!isPlatformNavigation ? navigation : platformNavigation
|
||||
).reduce<Record<string, NavigationItemType[]>>(
|
||||
(items, item, index) => {
|
||||
// We filter out the "more" separator in` desktop navigation
|
||||
if (item.name !== MORE_SEPARATOR_NAME) items.desktopNavigationItems.push(item);
|
||||
// Items for mobile bottom navigation
|
||||
if (index < moreSeparatorIndex + 1 && !item.onlyDesktop) {
|
||||
items.mobileNavigationBottomItems.push(item);
|
||||
} // Items for the "more" menu in mobile navigation
|
||||
else {
|
||||
items.mobileNavigationMoreItems.push(item);
|
||||
}
|
||||
return items;
|
||||
},
|
||||
{ desktopNavigationItems: [], mobileNavigationBottomItems: [], mobileNavigationMoreItems: [] }
|
||||
);
|
||||
const { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMoreItems } = (
|
||||
!isPlatformNavigation ? getNavigationItems(orgBranding) : platformNavigationItems
|
||||
).reduce<Record<string, NavigationItemType[]>>(
|
||||
(items, item, index) => {
|
||||
// We filter out the "more" separator in` desktop navigation
|
||||
if (item.name !== MORE_SEPARATOR_NAME) items.desktopNavigationItems.push(item);
|
||||
// Items for mobile bottom navigation
|
||||
if (index < moreSeparatorIndex + 1 && !item.onlyDesktop) {
|
||||
items.mobileNavigationBottomItems.push(item);
|
||||
} // Items for the "more" menu in mobile navigation
|
||||
else {
|
||||
items.mobileNavigationMoreItems.push(item);
|
||||
}
|
||||
return items;
|
||||
},
|
||||
{ desktopNavigationItems: [], mobileNavigationBottomItems: [], mobileNavigationMoreItems: [] }
|
||||
);
|
||||
|
||||
return { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMoreItems };
|
||||
return { desktopNavigationItems, mobileNavigationBottomItems, mobileNavigationMoreItems };
|
||||
}, [isPlatformNavigation, orgBranding]);
|
||||
};
|
||||
|
||||
export const Navigation = ({ isPlatformNavigation = false }: { isPlatformNavigation?: boolean }) => {
|
||||
const { desktopNavigationItems } = getDesktopNavigationItems(isPlatformNavigation);
|
||||
const { desktopNavigationItems } = useNavigationItems(isPlatformNavigation);
|
||||
|
||||
return (
|
||||
<nav className="mt-2 flex-1 md:px-2 lg:mt-4 lg:px-0">
|
||||
@@ -196,7 +213,7 @@ export function MobileNavigationContainer({
|
||||
|
||||
const MobileNavigation = ({ isPlatformNavigation = false }: { isPlatformNavigation?: boolean }) => {
|
||||
const isEmbed = useIsEmbed();
|
||||
const { mobileNavigationBottomItems } = getDesktopNavigationItems(isPlatformNavigation);
|
||||
const { mobileNavigationBottomItems } = useNavigationItems(isPlatformNavigation);
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -216,7 +233,7 @@ const MobileNavigation = ({ isPlatformNavigation = false }: { isPlatformNavigati
|
||||
};
|
||||
|
||||
export const MobileNavigationMoreItems = () => {
|
||||
const { mobileNavigationMoreItems } = getDesktopNavigationItems();
|
||||
const { mobileNavigationMoreItems } = useNavigationItems();
|
||||
|
||||
return (
|
||||
<ul className="border-subtle mt-2 rounded-md border">
|
||||
|
||||
Reference in New Issue
Block a user