V2 Settings Shell - Add Teams Section & UI fixes (#4347)
* Add teams to sidebar and fix UI * Clean up * V2 Multi-select (Team Select) (#4324) * --init * design improved * further fine tuning * more fixes * removed extra JSX tag * added story * NIT * revert to use of CheckedTeamSelect * Removes comments Co-authored-by: Peer Richelsen <peeroke@gmail.com> * fix: toggle alligment (#4361) * fix: add checked tranform for switch (#4357) * fixed input size on mobile, fixed settings (#4360) * fixing type errors * Mobile fixes * Tests fixes Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com> Co-authored-by: Peer Richelsen <peeroke@gmail.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: CarinaWolli <wollencarina@gmail.com> Co-authored-by: zomars <zomars@me.com>pull/4402/head
parent
ded1fce6fa
commit
7e52e3d295
|
@ -2,27 +2,18 @@ import { expect } from "@playwright/test";
|
|||
|
||||
import { test } from "./lib/fixtures";
|
||||
import {
|
||||
bookFirstEvent,
|
||||
bookTimeSlot,
|
||||
selectFirstAvailableTimeSlotNextMonth,
|
||||
selectSecondAvailableTimeSlotNextMonth,
|
||||
} from "./lib/testUtils";
|
||||
|
||||
test.describe.configure({ mode: "parallel" });
|
||||
test("dynamic booking", async ({ page, users }) => {
|
||||
const pro = await users.create();
|
||||
await pro.login();
|
||||
const free = await users.create({ plan: "FREE" });
|
||||
await page.goto(`/${pro.username}+${free.username}`);
|
||||
|
||||
test.describe("dynamic booking", () => {
|
||||
test.beforeEach(async ({ page, users }) => {
|
||||
const pro = await users.create();
|
||||
await pro.login();
|
||||
const free = await users.create({ plan: "FREE" });
|
||||
await page.goto(`/${pro.username}+${free.username}`);
|
||||
});
|
||||
|
||||
test.afterEach(async ({ users }) => {
|
||||
await users.deleteAll();
|
||||
});
|
||||
|
||||
test("book an event first day in next month", async ({ page }) => {
|
||||
await test.step("book an event first day in next month", async () => {
|
||||
// Click first event type
|
||||
await page.click('[data-testid="event-type-link"]');
|
||||
await selectFirstAvailableTimeSlotNextMonth(page);
|
||||
|
@ -30,9 +21,7 @@ test.describe("dynamic booking", () => {
|
|||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||
});
|
||||
|
||||
test("can reschedule a booking", async ({ page }) => {
|
||||
await bookFirstEvent(page);
|
||||
|
||||
await test.step("can reschedule a booking", async () => {
|
||||
// Logged in
|
||||
await page.goto("/bookings/upcoming");
|
||||
await page.locator('[data-testid="edit_booking"]').nth(0).click();
|
||||
|
@ -54,9 +43,7 @@ test.describe("dynamic booking", () => {
|
|||
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
|
||||
});
|
||||
|
||||
test("Can cancel the recently created booking", async ({ page }) => {
|
||||
await bookFirstEvent(page);
|
||||
|
||||
await test.step("Can cancel the recently created booking", async () => {
|
||||
await page.goto("/bookings/upcoming");
|
||||
await page.locator('[data-testid="cancel"]').first().click();
|
||||
await page.waitForNavigation({
|
||||
|
@ -72,4 +59,6 @@ test.describe("dynamic booking", () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
await users.deleteAll();
|
||||
});
|
||||
|
|
|
@ -1189,6 +1189,8 @@
|
|||
"add_new_form": "Add new form",
|
||||
"form_description": "Create your form to route a booker",
|
||||
"copy_link_to_form": "Copy link to form",
|
||||
"add_a_team": "Add a team",
|
||||
"saml_config": "SAML Config",
|
||||
"add_webhook_description": "Receive meeting data in real-time when something happens in Cal.com",
|
||||
"triggers_when": "Triggers when",
|
||||
"test_webhook": "Please ping test before creating.",
|
||||
|
@ -1203,7 +1205,6 @@
|
|||
"api_key_update_failed": "Error updating API key name",
|
||||
"embeds_title": "HTML iframe embed",
|
||||
"embeds_description": "Embed all your event types on your website",
|
||||
"pending_payment": "Pending payment",
|
||||
"create_first_api_key": "Create your first API key",
|
||||
"create_first_api_key_description": "API keys allow other apps to communicate with Cal.com",
|
||||
"back_to_signin": "Back to sign in",
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
|
||||
import { useSession } from "next-auth/react";
|
||||
import React, { ComponentProps, useState } from "react";
|
||||
import React, { ComponentProps, useEffect, useState } from "react";
|
||||
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import Button from "@calcom/ui/v2/core/Button";
|
||||
|
||||
import ErrorBoundary from "../../../ErrorBoundary";
|
||||
|
@ -10,7 +13,7 @@ import { Icon } from "../../../Icon";
|
|||
import { useMeta } from "../Meta";
|
||||
import Shell from "../Shell";
|
||||
import { VerticalTabItemProps } from "../navigation/tabs/VerticalTabItem";
|
||||
import VerticalTabs, { VerticalTabItem } from "../navigation/tabs/VerticalTabs";
|
||||
import { VerticalTabItem } from "../navigation/tabs/VerticalTabs";
|
||||
|
||||
const tabs: VerticalTabItemProps[] = [
|
||||
{
|
||||
|
@ -91,18 +94,164 @@ const useTabs = () => {
|
|||
};
|
||||
|
||||
const SettingsSidebarContainer = ({ className = "" }) => {
|
||||
const { t } = useLocale();
|
||||
const tabsWithPermissions = useTabs();
|
||||
const [teamMenuState, setTeamMenuState] =
|
||||
useState<{ teamId: number | undefined; teamMenuOpen: boolean }[]>();
|
||||
|
||||
const { data: teams } = trpc.useQuery(["viewer.teams.list"]);
|
||||
|
||||
useEffect(() => {
|
||||
if (teams) {
|
||||
const teamStates = teams?.map((team) => ({ teamId: team.id, teamMenuOpen: false }));
|
||||
setTeamMenuState(teamStates);
|
||||
}
|
||||
}, [teams]);
|
||||
|
||||
return (
|
||||
<VerticalTabs tabs={tabsWithPermissions} className={`py-3 pl-3 ${className}`}>
|
||||
<div className="desktop-only pt-4" />
|
||||
<VerticalTabItem
|
||||
name="Settings"
|
||||
href="/"
|
||||
icon={Icon.FiArrowLeft}
|
||||
textClassNames="text-md font-medium leading-none text-black"
|
||||
className="mb-1"
|
||||
/>
|
||||
</VerticalTabs>
|
||||
<nav
|
||||
className={`no-scrollbar flex w-56 flex-col space-y-1 overflow-scroll py-3 px-2 ${className}`}
|
||||
aria-label="Tabs">
|
||||
<>
|
||||
<div className="desktop-only pt-4" />
|
||||
<div>
|
||||
<VerticalTabItem
|
||||
name="Settings"
|
||||
href="/"
|
||||
icon={Icon.FiArrowLeft}
|
||||
textClassNames="text-md font-medium leading-none text-black"
|
||||
/>
|
||||
</div>
|
||||
{tabsWithPermissions.map((tab) => {
|
||||
return tab.name !== "teams" ? (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
className="group flex h-9 w-64 flex-row items-center rounded-md px-3 py-[10px] text-sm font-medium leading-none text-gray-600 hover:bg-gray-100 group-hover:text-gray-700 [&[aria-current='page']]:bg-gray-200 [&[aria-current='page']]:text-gray-900"
|
||||
key={tab.name}>
|
||||
{tab && tab.icon && (
|
||||
<tab.icon className="mr-[12px] h-[16px] w-[16px] self-start stroke-[2px] md:mt-0" />
|
||||
)}
|
||||
<p>{t(tab.name)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2">
|
||||
{tab.children?.map((tab) => (
|
||||
<VerticalTabItem
|
||||
key={tab.name}
|
||||
name={t(tab.name)}
|
||||
href={tab.href || "/"}
|
||||
textClassNames="px-3 text-gray-900 font-medium text-sm"
|
||||
disableChevron
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div>
|
||||
<div
|
||||
className="group flex h-9 w-64 flex-row items-center rounded-md px-3 py-[10px] text-sm font-medium leading-none text-gray-600 hover:bg-gray-100 group-hover:text-gray-700 [&[aria-current='page']]:bg-gray-200 [&[aria-current='page']]:text-gray-900"
|
||||
key={tab.name}>
|
||||
{tab && tab.icon && (
|
||||
<tab.icon className="mr-[12px] h-[16px] w-[16px] self-start stroke-[2px] md:mt-0" />
|
||||
)}
|
||||
<p>{t(tab.name)}</p>
|
||||
</div>
|
||||
{teams &&
|
||||
teamMenuState &&
|
||||
teams.map((team, index: number) => (
|
||||
<Collapsible
|
||||
key={team.id}
|
||||
open={teamMenuState[index].teamMenuOpen}
|
||||
onOpenChange={() =>
|
||||
setTeamMenuState([
|
||||
...teamMenuState,
|
||||
(teamMenuState[index] = {
|
||||
...teamMenuState[index],
|
||||
teamMenuOpen: !teamMenuState[index].teamMenuOpen,
|
||||
}),
|
||||
])
|
||||
}>
|
||||
<CollapsibleTrigger>
|
||||
<div
|
||||
className="flex h-9 w-64 flex-row items-center rounded-md px-3 py-[10px] text-sm font-medium leading-none hover:bg-gray-100 group-hover:text-gray-700 [&[aria-current='page']]:bg-gray-200 [&[aria-current='page']]:text-gray-900"
|
||||
onClick={() =>
|
||||
setTeamMenuState([
|
||||
...teamMenuState,
|
||||
(teamMenuState[index] = {
|
||||
...teamMenuState[index],
|
||||
teamMenuOpen: !teamMenuState[index].teamMenuOpen,
|
||||
}),
|
||||
])
|
||||
}>
|
||||
<div className="mr-[13px]">
|
||||
{teamMenuState[index].teamMenuOpen ? (
|
||||
<Icon.FiChevronDown />
|
||||
) : (
|
||||
<Icon.FiChevronRight />
|
||||
)}
|
||||
</div>
|
||||
{team.logo && (
|
||||
<img
|
||||
ref={team.logo}
|
||||
className=" ml-[12px] mr-[8px] h-[16px] w-[16px] self-start stroke-[2px] md:mt-0"
|
||||
alt={team.name || "Team logo"}
|
||||
/>
|
||||
)}
|
||||
<p>{team.name}</p>
|
||||
</div>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent>
|
||||
<VerticalTabItem
|
||||
name={t("profile")}
|
||||
href={`${WEBAPP_URL}/settings/my-account/appearance`}
|
||||
textClassNames="px-3 text-gray-900 font-medium text-sm"
|
||||
disableChevron
|
||||
/>
|
||||
<VerticalTabItem
|
||||
name={t("members")}
|
||||
href={`${WEBAPP_URL}/settings/my-account/appearance`}
|
||||
textClassNames="px-3 text-gray-900 font-medium text-sm"
|
||||
disableChevron
|
||||
/>
|
||||
{(team.role === "OWNER" || team.role === "ADMIN") && (
|
||||
<>
|
||||
<VerticalTabItem
|
||||
name={t("general")}
|
||||
href={`${WEBAPP_URL}/settings/my-account/appearance`}
|
||||
textClassNames="px-3 text-gray-900 font-medium text-sm"
|
||||
disableChevron
|
||||
/>
|
||||
<VerticalTabItem
|
||||
name={t("appearance")}
|
||||
href={`${WEBAPP_URL}/settings/my-account/appearance`}
|
||||
textClassNames="px-3 text-gray-900 font-medium text-sm"
|
||||
disableChevron
|
||||
/>
|
||||
<VerticalTabItem
|
||||
name={t("saml_config")}
|
||||
href={`${WEBAPP_URL}/settings/my-account/appearance`}
|
||||
textClassNames="px-3 text-gray-900 font-medium text-sm"
|
||||
disableChevron
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
))}
|
||||
<div
|
||||
className="group flex h-9 w-64 flex-row items-center rounded-md px-3 py-[10px] text-sm font-medium leading-none hover:bg-gray-100 group-hover:text-gray-700 [&[aria-current='page']]:bg-gray-200 [&[aria-current='page']]:text-gray-900"
|
||||
key={tab.name}>
|
||||
<Icon.FiPlus className=" mr-[10px] h-[16px] w-[16px] self-start stroke-[2px] md:mt-0" />
|
||||
<p>{t("add_a_team")}</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
</nav>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ export type VerticalTabItemProps = {
|
|||
className?: string;
|
||||
isChild?: boolean;
|
||||
hidden?: boolean;
|
||||
disableChevron?: boolean;
|
||||
} & (
|
||||
| {
|
||||
/** If you want to change query param tabName as per current tab */
|
||||
|
@ -32,7 +33,15 @@ export type VerticalTabItemProps = {
|
|||
}
|
||||
);
|
||||
|
||||
const VerticalTabItem: FC<VerticalTabItemProps> = ({ name, href, tabName, info, isChild, ...props }) => {
|
||||
const VerticalTabItem: FC<VerticalTabItemProps> = ({
|
||||
name,
|
||||
href,
|
||||
tabName,
|
||||
info,
|
||||
isChild,
|
||||
disableChevron,
|
||||
...props
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { t } = useLocale();
|
||||
let newHref = "";
|
||||
|
@ -73,12 +82,14 @@ const VerticalTabItem: FC<VerticalTabItemProps> = ({ name, href, tabName, info,
|
|||
props.className
|
||||
)}
|
||||
aria-current={isCurrent ? "page" : undefined}>
|
||||
{props.icon && <props.icon className="mr-[10px] h-[16px] w-[16px] self-start stroke-[2px]" />}
|
||||
{props.icon && (
|
||||
<props.icon className="mr-[10px] h-[16px] w-[16px] self-start stroke-[2px] md:mt-0" />
|
||||
)}
|
||||
<div>
|
||||
<p>{t(name)}</p>
|
||||
{info && <p className="pt-1 text-xs font-normal">{t(info)}</p>}
|
||||
</div>
|
||||
{isCurrent && (
|
||||
{!disableChevron && isCurrent && (
|
||||
<div className="ml-auto self-center">
|
||||
<Icon.FiChevronRight
|
||||
width={20}
|
||||
|
|
Loading…
Reference in New Issue