first commit
This commit is contained in:
135
calcom/packages/ui/components/createButton/CreateButton.tsx
Normal file
135
calcom/packages/ui/components/createButton/CreateButton.tsx
Normal file
@@ -0,0 +1,135 @@
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
|
||||
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { ButtonColor } from "@calcom/ui";
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuLabel,
|
||||
DropdownMenuTrigger,
|
||||
} from "@calcom/ui";
|
||||
|
||||
export interface Option {
|
||||
platform?: boolean;
|
||||
teamId: number | null | undefined; // if undefined, then it's a profile
|
||||
label: string | null;
|
||||
image: string | null;
|
||||
slug: string | null;
|
||||
}
|
||||
|
||||
export type CreateBtnProps = {
|
||||
options: Option[];
|
||||
createDialog?: () => JSX.Element;
|
||||
createFunction?: (teamId?: number, platform?: boolean) => void;
|
||||
subtitle?: string;
|
||||
buttonText?: string;
|
||||
isPending?: boolean;
|
||||
disableMobileButton?: boolean;
|
||||
"data-testid"?: string;
|
||||
color?: ButtonColor;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated use CreateButtonWithTeamsList instead
|
||||
*/
|
||||
export function CreateButton(props: CreateBtnProps) {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const searchParams = useCompatSearchParams();
|
||||
const pathname = usePathname();
|
||||
|
||||
const {
|
||||
createDialog,
|
||||
options,
|
||||
isPending,
|
||||
createFunction,
|
||||
buttonText,
|
||||
disableMobileButton,
|
||||
subtitle,
|
||||
...restProps
|
||||
} = props;
|
||||
const CreateDialog = createDialog ? createDialog() : null;
|
||||
|
||||
const hasTeams = !!options.find((option) => option.teamId);
|
||||
const platform = !!options.find((option) => option.platform);
|
||||
|
||||
// inject selection data into url for correct router history
|
||||
const openModal = (option: Option) => {
|
||||
const _searchParams = new URLSearchParams(searchParams ?? undefined);
|
||||
function setParamsIfDefined(key: string, value: string | number | boolean | null | undefined) {
|
||||
if (value !== undefined && value !== null) _searchParams.set(key, value.toString());
|
||||
}
|
||||
setParamsIfDefined("dialog", "new");
|
||||
setParamsIfDefined("eventPage", option.slug);
|
||||
setParamsIfDefined("teamId", option.teamId);
|
||||
if (!option.teamId) {
|
||||
_searchParams.delete("teamId");
|
||||
}
|
||||
router.push(`${pathname}?${_searchParams.toString()}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{!hasTeams && !platform ? (
|
||||
<Button
|
||||
onClick={() =>
|
||||
!!CreateDialog
|
||||
? openModal(options[0])
|
||||
: createFunction
|
||||
? createFunction(options[0].teamId || undefined)
|
||||
: null
|
||||
}
|
||||
data-testid="create-button"
|
||||
StartIcon="plus"
|
||||
loading={isPending}
|
||||
variant={disableMobileButton ? "button" : "fab"}
|
||||
{...restProps}>
|
||||
{buttonText ? buttonText : t("new")}
|
||||
</Button>
|
||||
) : (
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant={disableMobileButton ? "button" : "fab"}
|
||||
StartIcon="plus"
|
||||
data-testid="create-button-dropdown"
|
||||
loading={isPending}
|
||||
{...restProps}>
|
||||
{buttonText ? buttonText : t("new")}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent sideOffset={14} align="end">
|
||||
<DropdownMenuLabel>
|
||||
<div className="w-48 text-left text-xs">{subtitle}</div>
|
||||
</DropdownMenuLabel>
|
||||
{options.map((option, idx) => (
|
||||
<DropdownMenuItem key={option.label}>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
data-testid={`option${option.teamId ? "-team" : ""}-${idx}`}
|
||||
CustomStartIcon={<Avatar alt={option.label || ""} imageSrc={option.image} size="sm" />}
|
||||
onClick={() =>
|
||||
!!CreateDialog
|
||||
? openModal(option)
|
||||
: createFunction
|
||||
? createFunction(option.teamId || undefined, option.platform)
|
||||
: null
|
||||
}>
|
||||
{" "}
|
||||
{/*improve this code */}
|
||||
<span>{option.label}</span>
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
)}
|
||||
{searchParams?.get("dialog") === "new" && CreateDialog}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
import type { CreateBtnProps, Option } from "./CreateButton";
|
||||
import { CreateButton } from "./CreateButton";
|
||||
|
||||
export function CreateButtonWithTeamsList(
|
||||
props: Omit<CreateBtnProps, "options"> & {
|
||||
onlyShowWithTeams?: boolean;
|
||||
onlyShowWithNoTeams?: boolean;
|
||||
isAdmin?: boolean;
|
||||
includeOrg?: boolean;
|
||||
}
|
||||
) {
|
||||
const query = trpc.viewer.teamsAndUserProfilesQuery.useQuery({ includeOrg: props.includeOrg });
|
||||
if (!query.data) return null;
|
||||
|
||||
const teamsAndUserProfiles: Option[] = query.data
|
||||
.filter((profile) => !profile.readOnly)
|
||||
.map((profile) => {
|
||||
return {
|
||||
teamId: profile.teamId,
|
||||
label: profile.name || profile.slug,
|
||||
image: profile.image,
|
||||
slug: profile.slug,
|
||||
};
|
||||
});
|
||||
|
||||
if (props.isAdmin) {
|
||||
teamsAndUserProfiles.push({
|
||||
platform: true,
|
||||
label: "Platform",
|
||||
image: null,
|
||||
slug: null,
|
||||
teamId: null,
|
||||
});
|
||||
}
|
||||
|
||||
if (props.onlyShowWithTeams && teamsAndUserProfiles.length < 2) return null;
|
||||
|
||||
if (props.onlyShowWithNoTeams && teamsAndUserProfiles.length > 1) return null;
|
||||
|
||||
return <CreateButton {...props} options={teamsAndUserProfiles} />;
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { render, screen, fireEvent } from "@testing-library/react";
|
||||
import { vi } from "vitest";
|
||||
|
||||
import type { CreateBtnProps } from "./CreateButton";
|
||||
import { CreateButtonWithTeamsList } from "./CreateButtonWithTeamsList";
|
||||
|
||||
const runtimeMock = async (data: Array<any>) => {
|
||||
const updatedTrpc = {
|
||||
viewer: {
|
||||
teamsAndUserProfilesQuery: {
|
||||
useQuery() {
|
||||
return {
|
||||
data: data,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const mockedLib = (await import("@calcom/trpc/react")) as any;
|
||||
mockedLib.trpc = updatedTrpc;
|
||||
};
|
||||
const renderCreateButton = (
|
||||
props: Omit<CreateBtnProps, "options"> & { onlyShowWithTeams?: boolean; onlyShowWithNoTeams?: boolean }
|
||||
) => {
|
||||
return render(<CreateButtonWithTeamsList {...props} />);
|
||||
};
|
||||
|
||||
describe("Create Button Tests", () => {
|
||||
describe("Create Button Tests With Valid Team", () => {
|
||||
beforeAll(async () => {
|
||||
await runtimeMock([
|
||||
{
|
||||
teamId: 1,
|
||||
name: "test",
|
||||
slug: "create-button-test",
|
||||
image: "image",
|
||||
},
|
||||
]);
|
||||
});
|
||||
test("Should render the create-button-dropdown button", () => {
|
||||
const createFunction = vi.fn();
|
||||
renderCreateButton({ createFunction });
|
||||
|
||||
const createButton = screen.getByTestId("create-button-dropdown");
|
||||
expect(createButton).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Create Button Tests With One Null Team", () => {
|
||||
beforeAll(async () => {
|
||||
await runtimeMock([
|
||||
{
|
||||
teamId: null,
|
||||
name: "test",
|
||||
slug: "create-button-test",
|
||||
image: "image",
|
||||
readOnly: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Should render only the create-button button", () => {
|
||||
const createFunction = vi.fn();
|
||||
renderCreateButton({ createFunction });
|
||||
|
||||
const createButton = screen.getByTestId("create-button");
|
||||
expect(screen.queryByTestId("create-button-dropdown")).not.toBeInTheDocument();
|
||||
expect(createButton).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(createButton);
|
||||
expect(createFunction).toBeCalled();
|
||||
});
|
||||
|
||||
test("Should render nothing when teamsAndUserProfiles is less than 2 and onlyShowWithTeams is true", () => {
|
||||
renderCreateButton({ onlyShowWithTeams: true });
|
||||
|
||||
expect(screen.queryByTestId("create-button")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("create-button-dropdown")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe("Create Button Tests With Multiple Null Teams", () => {
|
||||
beforeAll(async () => {
|
||||
await runtimeMock([
|
||||
{
|
||||
teamId: null,
|
||||
name: "test",
|
||||
slug: "create-button-test",
|
||||
image: "image",
|
||||
readOnly: false,
|
||||
},
|
||||
{
|
||||
teamId: null,
|
||||
name: "test2",
|
||||
slug: "create-button-test2",
|
||||
image: "image2",
|
||||
readOnly: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test("Should render only the create-button button", () => {
|
||||
const createFunction = vi.fn();
|
||||
renderCreateButton({ createFunction });
|
||||
|
||||
const createButton = screen.getByTestId("create-button");
|
||||
expect(screen.queryByTestId("create-button-dropdown")).not.toBeInTheDocument();
|
||||
expect(createButton).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(createButton);
|
||||
expect(createFunction).toBeCalled();
|
||||
});
|
||||
|
||||
test("Should render nothing when teamsAndUserProfiles is greater than 1 and onlyShowWithNoTeams is true", () => {
|
||||
renderCreateButton({ onlyShowWithNoTeams: true });
|
||||
|
||||
expect(screen.queryByTestId("create-button")).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId("create-button-dropdown")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
2
calcom/packages/ui/components/createButton/index.ts
Normal file
2
calcom/packages/ui/components/createButton/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { CreateButton } from "./CreateButton";
|
||||
export { CreateButtonWithTeamsList } from "./CreateButtonWithTeamsList";
|
||||
Reference in New Issue
Block a user