feat: advanced tab platform wrapper (#16941)
* fixed error in advanced tab atom build -- used `DOMPurify` instead of `sanitize-html` * hide BookerLayoutSelector for platform and removePlatformClientIdFromEmail * fixup! Merge branch 'main' into event-advanced-platform-wrapper * undo linting changes * undo linting changes * fix: active state of tabs items --------- Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com> Co-authored-by: Morgan Vernay <morgan@cal.com>
This commit is contained in:
co-authored by
Morgan
Morgan Vernay
parent
1f08bc5a4f
commit
99caae8665
@@ -28,7 +28,7 @@
|
||||
"dependencies": {
|
||||
"@calcom/platform-constants": "*",
|
||||
"@calcom/platform-enums": "*",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.44",
|
||||
"@calcom/platform-libraries": "npm:@calcom/platform-libraries@0.0.45",
|
||||
"@calcom/platform-libraries-0.0.2": "npm:@calcom/platform-libraries@0.0.2",
|
||||
"@calcom/platform-types": "*",
|
||||
"@calcom/platform-utils": "*",
|
||||
|
||||
@@ -79,6 +79,7 @@
|
||||
"bcp-47-match": "^2.0.3",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"classnames": "^2.3.1",
|
||||
"dompurify": "^3.1.7",
|
||||
"dotenv-cli": "^6.0.0",
|
||||
"entities": "^4.4.0",
|
||||
"eslint-config-next": "^13.2.1",
|
||||
@@ -152,6 +153,7 @@
|
||||
"@types/async": "^3.2.15",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/detect-port": "^1.3.2",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/glidejs__glide": "^3.4.2",
|
||||
"@types/lodash": "^4.14.182",
|
||||
"@types/markdown-it": "^12.2.3",
|
||||
|
||||
@@ -34,14 +34,20 @@ async function enableContactOwnerOverride(page: Page) {
|
||||
async function selectFirstAttributeOption({ fromLocator }: { fromLocator: Locator }) {
|
||||
await selectOptionUsingLocator({
|
||||
locator: fromLocator,
|
||||
option: 1
|
||||
option: 1,
|
||||
});
|
||||
}
|
||||
|
||||
async function selectFirstValueForAttributeValue({ fromLocator, option }: { fromLocator: Locator, option: number }) {
|
||||
async function selectFirstValueForAttributeValue({
|
||||
fromLocator,
|
||||
option,
|
||||
}: {
|
||||
fromLocator: Locator;
|
||||
option: number;
|
||||
}) {
|
||||
await selectOptionUsingLocator({
|
||||
locator: fromLocator,
|
||||
option
|
||||
option,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,7 +65,7 @@ async function addAttributeRoutingRule(page: Page) {
|
||||
await selectFirstValueForAttributeValue({
|
||||
fromLocator: attributeValueSelector,
|
||||
// Select 'Value of Field Short Text' option
|
||||
option: numOfOptionsInAttribute + 1
|
||||
option: numOfOptionsInAttribute + 1,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -571,7 +577,7 @@ test.describe("Routing Forms", () => {
|
||||
await page.click('[data-testid="test-preview"]');
|
||||
await page.fill('[data-testid="form-field-short-text"]', "large");
|
||||
await page.click('[data-testid="test-routing"]');
|
||||
await page.waitForSelector('text=@example.com');
|
||||
await page.waitForSelector("text=@example.com");
|
||||
await page.click('[data-testid="dialog-rejection"]');
|
||||
})();
|
||||
|
||||
@@ -579,9 +585,8 @@ test.describe("Routing Forms", () => {
|
||||
await page.click('[data-testid="test-preview"]');
|
||||
await page.fill('[data-testid="form-field-short-text"]', "medium");
|
||||
await page.click('[data-testid="test-routing"]');
|
||||
await page.waitForSelector('text=No matching members.');
|
||||
await page.waitForSelector("text=No matching members.");
|
||||
await page.click('[data-testid="dialog-rejection"]');
|
||||
|
||||
})();
|
||||
});
|
||||
});
|
||||
@@ -751,15 +756,12 @@ async function selectOption({
|
||||
.click();
|
||||
}
|
||||
|
||||
export async function selectOptionUsingLocator({
|
||||
locator,
|
||||
option,
|
||||
}: {
|
||||
locator: Locator;
|
||||
option: number;
|
||||
}) {
|
||||
export async function selectOptionUsingLocator({ locator, option }: { locator: Locator; option: number }) {
|
||||
await locator.click();
|
||||
await locator.locator('[id*="react-select-"][aria-disabled]').nth(option - 1).click();
|
||||
await locator
|
||||
.locator('[id*="react-select-"][aria-disabled]')
|
||||
.nth(option - 1)
|
||||
.click();
|
||||
}
|
||||
|
||||
async function verifyFieldOptionsInRule(options: string[], page: Page) {
|
||||
|
||||
@@ -2,7 +2,6 @@ import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { getRoutedTeamMemberIdsFromSearchParams } from "@calcom/lib/bookings/getRoutedTeamMemberIdsFromSearchParams";
|
||||
import { fromEntriesWithDuplicateKeys } from "@calcom/lib/fromEntriesWithDuplicateKeys";
|
||||
import { parseRecurringDates } from "@calcom/lib/parse-dates";
|
||||
|
||||
import type { BookerEvent, BookingCreateBody, RecurringBookingCreateBody } from "../../types";
|
||||
|
||||
@@ -39,7 +39,13 @@ export async function loadAndValidateUsers({
|
||||
routedTeamMemberIds,
|
||||
contactOwnerEmail,
|
||||
}: InputProps): Promise<Users> {
|
||||
let users: Users = await loadUsers({ eventType, dynamicUserList, req, routedTeamMemberIds, contactOwnerEmail });
|
||||
let users: Users = await loadUsers({
|
||||
eventType,
|
||||
dynamicUserList,
|
||||
req,
|
||||
routedTeamMemberIds,
|
||||
contactOwnerEmail,
|
||||
});
|
||||
const isDynamicAllowed = !users.some((user) => !user.allowDynamicBooking);
|
||||
if (!isDynamicAllowed && !eventTypeId) {
|
||||
logger.warn({
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import dynamic from "next/dynamic";
|
||||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import type { z } from "zod";
|
||||
|
||||
import type { EventAdvancedWebWrapperProps } from "@calcom/atoms/event-types/wrappers/EventAdvancedWebWrapper";
|
||||
import { useAtomsContext, useIsPlatform } from "@calcom/atoms/monorepo";
|
||||
import type { EventNameObjectType } from "@calcom/core/event";
|
||||
import { getEventName } from "@calcom/core/event";
|
||||
import getLocationsOptionsForSelect from "@calcom/features/bookings/lib/getLocationOptionsForSelect";
|
||||
@@ -14,7 +13,7 @@ import {
|
||||
allowDisablingHostConfirmationEmails,
|
||||
} from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails";
|
||||
import { MultiplePrivateLinksController } from "@calcom/features/eventtypes/components";
|
||||
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
|
||||
import type { FormValues, EventTypeSetupProps } from "@calcom/features/eventtypes/lib/types";
|
||||
import { FormBuilder } from "@calcom/features/form-builder/FormBuilder";
|
||||
import type { fieldSchema } from "@calcom/features/form-builder/schema";
|
||||
import type { EditableSchema } from "@calcom/features/form-builder/schema";
|
||||
@@ -39,21 +38,26 @@ import {
|
||||
SettingsToggle,
|
||||
Switch,
|
||||
TextField,
|
||||
showToast,
|
||||
ColorPicker,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import CustomEventTypeModal from "./CustomEventTypeModal";
|
||||
import { DisableAllEmailsSetting } from "./DisableAllEmailsSetting";
|
||||
import RequiresConfirmationController from "./RequiresConfirmationController";
|
||||
|
||||
type BookingField = z.infer<typeof fieldSchema>;
|
||||
|
||||
const CustomEventTypeModal = dynamic(() => import("./CustomEventTypeModal"));
|
||||
|
||||
export type EventAdvancedTabProps = EventAdvancedWebWrapperProps & {
|
||||
calendarsQueryData?: RouterOutputs["viewer"]["connectedCalendars"];
|
||||
user?: RouterOutputs["viewer"]["me"];
|
||||
export type EventAdvancedBaseProps = Pick<EventTypeSetupProps, "eventType" | "team"> & {
|
||||
user?: Partial<
|
||||
Pick<RouterOutputs["viewer"]["me"], "email" | "secondaryEmails" | "theme" | "defaultBookerLayouts">
|
||||
>;
|
||||
isUserLoading?: boolean;
|
||||
showToast: (message: string, variant: "success" | "warning" | "error") => void;
|
||||
};
|
||||
|
||||
export type EventAdvancedTabProps = EventAdvancedBaseProps & {
|
||||
calendarsQueryData?: RouterOutputs["viewer"]["connectedCalendars"];
|
||||
showBookerLayoutSelector: boolean;
|
||||
};
|
||||
|
||||
export const EventAdvancedTab = ({
|
||||
@@ -62,7 +66,11 @@ export const EventAdvancedTab = ({
|
||||
calendarsQueryData,
|
||||
user,
|
||||
isUserLoading,
|
||||
showToast,
|
||||
showBookerLayoutSelector,
|
||||
}: EventAdvancedTabProps) => {
|
||||
const isPlatform = useIsPlatform();
|
||||
const platformContext = useAtomsContext();
|
||||
const formMethods = useFormContext<FormValues>();
|
||||
const { t } = useLocale();
|
||||
const [showEventNameTip, setShowEventNameTip] = useState(false);
|
||||
@@ -169,7 +177,7 @@ export const EventAdvancedTab = ({
|
||||
const displayDestinationCalendarSelector =
|
||||
!!calendarsQueryData?.connectedCalendars?.length && (!team || isChildrenManagedEventType);
|
||||
|
||||
const verifiedSecondaryEmails = [
|
||||
let verifiedSecondaryEmails = [
|
||||
{
|
||||
label: user?.email || "",
|
||||
value: -1,
|
||||
@@ -178,6 +186,20 @@ export const EventAdvancedTab = ({
|
||||
.filter((secondaryEmail) => !!secondaryEmail.emailVerified)
|
||||
.map((secondaryEmail) => ({ label: secondaryEmail.email, value: secondaryEmail.id })),
|
||||
];
|
||||
|
||||
const removePlatformClientIdFromEmail = (email: string, clientId: string) =>
|
||||
email.replace(`+${clientId}`, "");
|
||||
|
||||
let userEmail = user?.email || "";
|
||||
|
||||
if (isPlatform && platformContext.clientId) {
|
||||
verifiedSecondaryEmails = verifiedSecondaryEmails.map((email) => ({
|
||||
...email,
|
||||
label: removePlatformClientIdFromEmail(email.label, platformContext.clientId),
|
||||
}));
|
||||
userEmail = removePlatformClientIdFromEmail(userEmail, platformContext.clientId);
|
||||
}
|
||||
|
||||
const selectedSecondaryEmailId = formMethods.getValues("secondaryEmailId") || -1;
|
||||
return (
|
||||
<div className="flex flex-col space-y-4">
|
||||
@@ -257,7 +279,7 @@ export const EventAdvancedTab = ({
|
||||
placeholder={
|
||||
selectedSecondaryEmailId === -1 && (
|
||||
<span className="text-default min-w-0 overflow-hidden truncate whitespace-nowrap">
|
||||
<Badge variant="blue">{t("default")}</Badge> {user?.email || ""}
|
||||
<Badge variant="blue">{t("default")}</Badge> {userEmail}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -275,13 +297,15 @@ export const EventAdvancedTab = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<BookerLayoutSelector
|
||||
fallbackToUserSettings
|
||||
isDark={selectedThemeIsDark}
|
||||
isOuterBorder={true}
|
||||
user={user}
|
||||
isUserLoading={isUserLoading}
|
||||
/>
|
||||
{showBookerLayoutSelector && (
|
||||
<BookerLayoutSelector
|
||||
fallbackToUserSettings
|
||||
isDark={selectedThemeIsDark}
|
||||
isOuterBorder={true}
|
||||
user={user}
|
||||
isUserLoading={isUserLoading}
|
||||
/>
|
||||
)}
|
||||
<div className="border-subtle space-y-6 rounded-lg border p-6">
|
||||
<FormBuilder
|
||||
title={t("booking_questions_title")}
|
||||
@@ -419,45 +443,47 @@ export const EventAdvancedTab = ({
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="multiplePrivateLinks"
|
||||
render={() => {
|
||||
return (
|
||||
<SettingsToggle
|
||||
labelClassName="text-sm"
|
||||
toggleSwitchAtTheEnd={true}
|
||||
switchContainerClassName={classNames(
|
||||
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
|
||||
multiplePrivateLinksVisible && "rounded-b-none"
|
||||
)}
|
||||
childrenClassName="lg:ml-0"
|
||||
data-testid="multiplePrivateLinksCheck"
|
||||
title={t("multiple_private_links_title")}
|
||||
{...multiplePrivateLinksLocked}
|
||||
description={t("multiple_private_links_description", { appName: APP_NAME })}
|
||||
tooltip={isManagedEventType ? t("managed_event_field_parent_control_disabled") : ""}
|
||||
checked={multiplePrivateLinksVisible}
|
||||
onCheckedChange={(e) => {
|
||||
if (!e) {
|
||||
formMethods.setValue("multiplePrivateLinks", [], { shouldDirty: true });
|
||||
} else {
|
||||
formMethods.setValue(
|
||||
"multiplePrivateLinks",
|
||||
[generateHashedLink(formMethods.getValues("users")[0]?.id ?? team?.id)],
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
}
|
||||
setMultiplePrivateLinksVisible(e);
|
||||
}}>
|
||||
{!isManagedEventType && (
|
||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||
<MultiplePrivateLinksController team={team} bookerUrl={eventType.bookerUrl} />
|
||||
</div>
|
||||
)}
|
||||
</SettingsToggle>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
{!isPlatform && (
|
||||
<Controller
|
||||
name="multiplePrivateLinks"
|
||||
render={() => {
|
||||
return (
|
||||
<SettingsToggle
|
||||
labelClassName="text-sm"
|
||||
toggleSwitchAtTheEnd={true}
|
||||
switchContainerClassName={classNames(
|
||||
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
|
||||
multiplePrivateLinksVisible && "rounded-b-none"
|
||||
)}
|
||||
childrenClassName="lg:ml-0"
|
||||
data-testid="multiplePrivateLinksCheck"
|
||||
title={t("multiple_private_links_title")}
|
||||
{...multiplePrivateLinksLocked}
|
||||
description={t("multiple_private_links_description", { appName: APP_NAME })}
|
||||
tooltip={isManagedEventType ? t("managed_event_field_parent_control_disabled") : ""}
|
||||
checked={multiplePrivateLinksVisible}
|
||||
onCheckedChange={(e) => {
|
||||
if (!e) {
|
||||
formMethods.setValue("multiplePrivateLinks", [], { shouldDirty: true });
|
||||
} else {
|
||||
formMethods.setValue(
|
||||
"multiplePrivateLinks",
|
||||
[generateHashedLink(formMethods.getValues("users")[0]?.id ?? team?.id)],
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
}
|
||||
setMultiplePrivateLinksVisible(e);
|
||||
}}>
|
||||
{!isManagedEventType && (
|
||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||
<MultiplePrivateLinksController team={team} bookerUrl={eventType.bookerUrl} />
|
||||
</div>
|
||||
)}
|
||||
</SettingsToggle>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Controller
|
||||
name="seatsPerTimeSlotEnabled"
|
||||
render={({ field: { value, onChange } }) => (
|
||||
|
||||
@@ -7,7 +7,7 @@ import type { z } from "zod";
|
||||
import { classNames } from "@calcom/lib";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
|
||||
import { markdownToSafeHTMLClient } from "@calcom/lib/markdownToSafeHTMLClient";
|
||||
import turndown from "@calcom/lib/turndownService";
|
||||
import {
|
||||
Badge,
|
||||
@@ -153,7 +153,7 @@ export const FormBuilder = function FormBuilder({
|
||||
}
|
||||
|
||||
if (fieldsThatSupportLabelAsSafeHtml.includes(field.type)) {
|
||||
field = { ...field, labelAsSafeHtml: markdownToSafeHTML(field.label ?? "") };
|
||||
field = { ...field, labelAsSafeHtml: markdownToSafeHTMLClient(field.label ?? "") };
|
||||
}
|
||||
const numOptions = options?.length ?? 0;
|
||||
const firstOptionInput =
|
||||
@@ -692,7 +692,7 @@ function FieldLabel({ field }: { field: RhfFormField }) {
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
// Derive from field.label because label might change in b/w and field.labelAsSafeHtml will not be updated.
|
||||
__html: markdownToSafeHTML(field.label || "") || t(field.defaultLabel || ""),
|
||||
__html: markdownToSafeHTMLClient(field.label || "") || t(field.defaultLabel || ""),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ type BookerLayoutSelectorProps = {
|
||||
isLoading?: boolean;
|
||||
isDisabled?: boolean;
|
||||
isOuterBorder?: boolean;
|
||||
user?: RouterOutputs["viewer"]["me"];
|
||||
user?: Partial<Pick<RouterOutputs["viewer"]["me"], "defaultBookerLayouts">>;
|
||||
isUserLoading?: boolean;
|
||||
};
|
||||
|
||||
@@ -104,7 +104,7 @@ type BookerLayoutFieldsProps = {
|
||||
showUserSettings: boolean;
|
||||
isDark?: boolean;
|
||||
isOuterBorder?: boolean;
|
||||
user?: RouterOutputs["viewer"]["me"];
|
||||
user?: Partial<Pick<RouterOutputs["viewer"]["me"], "defaultBookerLayouts">>;
|
||||
isUserLoading?: boolean;
|
||||
};
|
||||
|
||||
|
||||
@@ -21,4 +21,4 @@ export function fromEntriesWithDuplicateKeys(entries: IterableIterator<[string,
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import DOMPurify from "dompurify";
|
||||
|
||||
import { md } from "@calcom/lib/markdownIt";
|
||||
|
||||
if (typeof window == "undefined") {
|
||||
console.warn(
|
||||
"`markdownToSafeHTMLClient` should not be used on the server side. use markdownToSafeHTML instead"
|
||||
);
|
||||
}
|
||||
|
||||
export function markdownToSafeHTMLClient(markdown: string | null) {
|
||||
if (!markdown) return "";
|
||||
|
||||
const html = md.render(markdown);
|
||||
|
||||
const safeHTML = DOMPurify.sanitize(html);
|
||||
|
||||
const safeHTMLWithListFormatting = safeHTML
|
||||
.replace(
|
||||
/<ul>/g,
|
||||
"<ul style='list-style-type: disc; list-style-position: inside; margin-left: 12px; margin-bottom: 4px'>"
|
||||
)
|
||||
.replace(
|
||||
/<ol>/g,
|
||||
"<ol style='list-style-type: decimal; list-style-position: inside; margin-left: 12px; margin-bottom: 4px'>"
|
||||
)
|
||||
.replace(/<a\s+href=/g, "<a target='_blank' class='text-blue-500 hover:text-blue-600' href=");
|
||||
|
||||
return safeHTMLWithListFormatting;
|
||||
}
|
||||
@@ -60,12 +60,14 @@ export const usePlatformTabsNavigations = ({ formMethods, eventType, team, tabs
|
||||
onClick: (tab) => {
|
||||
setCurrentTab(tab);
|
||||
},
|
||||
currentTab,
|
||||
});
|
||||
|
||||
if (!requirePayment) {
|
||||
navigation.splice(3, 0, {
|
||||
name: "recurring",
|
||||
onClick: () => setCurrentTab("recurring"),
|
||||
isActive: currentTab === "recurring",
|
||||
href: `${url}?tabName=recurring`,
|
||||
icon: "repeat",
|
||||
info: `recurring_event_tab_description`,
|
||||
@@ -75,6 +77,7 @@ export const usePlatformTabsNavigations = ({ formMethods, eventType, team, tabs
|
||||
navigation.splice(1, 0, {
|
||||
name: "availability",
|
||||
onClick: () => setCurrentTab("availability"),
|
||||
isActive: currentTab === "availability",
|
||||
href: `${url}?tabName=availability`,
|
||||
icon: "calendar",
|
||||
info:
|
||||
@@ -96,6 +99,7 @@ export const usePlatformTabsNavigations = ({ formMethods, eventType, team, tabs
|
||||
navigation.splice(2, 0, {
|
||||
name: "assignment",
|
||||
onClick: () => setCurrentTab("team"),
|
||||
isActive: currentTab === "team",
|
||||
href: `${url}?tabName=team`,
|
||||
icon: "users",
|
||||
info: `${t(watchSchedulingType?.toLowerCase() ?? "")}${
|
||||
@@ -133,15 +137,17 @@ type getNavigationProps = {
|
||||
tabs: PlatformTabs[];
|
||||
url: string;
|
||||
onClick: (tab: PlatformTabs) => void;
|
||||
currentTab: PlatformTabs;
|
||||
};
|
||||
|
||||
function getNavigation({ length, multipleDuration, t, tabs, url, onClick }: getNavigationProps) {
|
||||
function getNavigation({ length, multipleDuration, t, tabs, url, onClick, currentTab }: getNavigationProps) {
|
||||
const duration = multipleDuration?.map((duration) => ` ${duration}`) || length;
|
||||
const tabsNavigation: VerticalTabItemProps[] = [];
|
||||
tabs.includes("setup") &&
|
||||
tabsNavigation.push({
|
||||
name: "event_setup_tab_title",
|
||||
onClick: () => onClick("setup"),
|
||||
isActive: currentTab === "setup",
|
||||
href: `${url}?tabName=setup`,
|
||||
icon: "link",
|
||||
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
|
||||
@@ -150,6 +156,7 @@ function getNavigation({ length, multipleDuration, t, tabs, url, onClick }: getN
|
||||
tabsNavigation.push({
|
||||
name: "event_limit_tab_title",
|
||||
onClick: () => onClick("limits"),
|
||||
isActive: currentTab === "limits",
|
||||
href: `${url}?tabName=limits`,
|
||||
icon: "clock",
|
||||
info: `event_limit_tab_description`,
|
||||
@@ -159,6 +166,7 @@ function getNavigation({ length, multipleDuration, t, tabs, url, onClick }: getN
|
||||
tabsNavigation.push({
|
||||
name: "event_advanced_tab_title",
|
||||
onClick: () => onClick("advanced"),
|
||||
isActive: currentTab === "advanced",
|
||||
href: `${url}?tabName=advanced`,
|
||||
icon: "sliders-vertical",
|
||||
info: `event_advanced_tab_description`,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { EventAdvancedBaseProps } from "@calcom/features/eventtypes/components/tabs/advanced/EventAdvancedTab";
|
||||
import { EventAdvancedTab } from "@calcom/features/eventtypes/components/tabs/advanced/EventAdvancedTab";
|
||||
|
||||
import { useConnectedCalendars } from "../../hooks/useConnectedCalendars";
|
||||
|
||||
const EventAdvancedPlatformWrapper = (props: EventAdvancedBaseProps) => {
|
||||
const { data: connectedCalendarsQuery } = useConnectedCalendars({});
|
||||
return (
|
||||
<EventAdvancedTab
|
||||
{...props}
|
||||
calendarsQueryData={connectedCalendarsQuery}
|
||||
showBookerLayoutSelector={false}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventAdvancedPlatformWrapper;
|
||||
@@ -1,25 +1,14 @@
|
||||
import type { EventAdvancedBaseProps } from "@calcom/features/eventtypes/components/tabs/advanced/EventAdvancedTab";
|
||||
import { EventAdvancedTab } from "@calcom/features/eventtypes/components/tabs/advanced/EventAdvancedTab";
|
||||
import type { EventTypeSetupProps } from "@calcom/features/eventtypes/lib/types";
|
||||
import type { RouterOutputs } from "@calcom/trpc/react";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
|
||||
export type EventAdvancedWebWrapperProps = Pick<EventTypeSetupProps, "eventType" | "team"> & {
|
||||
loggedInUser?: RouterOutputs["viewer"]["me"];
|
||||
isLoggedInUserPending?: boolean;
|
||||
};
|
||||
|
||||
const EventAdvancedWebWrapper = ({
|
||||
loggedInUser,
|
||||
isLoggedInUserPending,
|
||||
...props
|
||||
}: EventAdvancedWebWrapperProps) => {
|
||||
const EventAdvancedWebWrapper = ({ ...props }: EventAdvancedBaseProps) => {
|
||||
const connectedCalendarsQuery = trpc.viewer.connectedCalendars.useQuery();
|
||||
return (
|
||||
<EventAdvancedTab
|
||||
{...props}
|
||||
calendarsQueryData={connectedCalendarsQuery.data}
|
||||
user={loggedInUser}
|
||||
isUserLoading={isLoggedInUserPending}
|
||||
showBookerLayoutSelector={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@ import type { EventTypeSetupProps, FormValues, TabMap } from "@calcom/features/e
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { SchedulingType } from "@calcom/prisma/enums";
|
||||
|
||||
import { useMe } from "../../hooks/useMe";
|
||||
import { AtomsWrapper } from "../../src/components/atoms-wrapper";
|
||||
import { useToast } from "../../src/components/ui/use-toast";
|
||||
import { useAtomsEventTypeById, QUERY_KEY as ATOM_EVENT_TYPE_QUERY_KEY } from "../hooks/useAtomEventTypeById";
|
||||
@@ -17,6 +18,7 @@ import { useAtomUpdateEventType } from "../hooks/useAtomUpdateEventType";
|
||||
import { useEventTypeForm } from "../hooks/useEventTypeForm";
|
||||
import { useHandleRouteChange } from "../hooks/useHandleRouteChange";
|
||||
import { usePlatformTabsNavigations } from "../hooks/usePlatformTabsNavigations";
|
||||
import EventAdvancedPlatformWrapper from "./EventAdvancedPlatformWrapper";
|
||||
import EventLimitsTabPlatformWrapper from "./EventLimitsTabPlatformWrapper";
|
||||
import EventRecurringTabPlatformWrapper from "./EventRecurringTabPlatformWrapper";
|
||||
import SetupTab from "./EventSetupTabPlatformWrapper";
|
||||
@@ -42,6 +44,7 @@ const EventType = ({
|
||||
const [pendingRoute, setPendingRoute] = useState("");
|
||||
const { eventType, locationOptions, team, teamMembers, destinationCalendar } = props;
|
||||
const [slugExistsChildrenDialogOpen, setSlugExistsChildrenDialogOpen] = useState<ChildrenEventType[]>([]);
|
||||
const { data: user, isLoading: isUserLoading } = useMe();
|
||||
|
||||
const updateMutation = useAtomUpdateEventType({
|
||||
onSuccess: async () => {
|
||||
@@ -73,6 +76,10 @@ const EventType = ({
|
||||
const { form, handleSubmit } = useEventTypeForm({ eventType, onSubmit: updateMutation.mutate });
|
||||
const slug = form.watch("slug") ?? eventType.slug;
|
||||
|
||||
const showToast = (message: string, variant: "success" | "warning" | "error") => {
|
||||
toast({ description: message });
|
||||
};
|
||||
|
||||
const tabMap = {
|
||||
setup: tabs.includes("setup") ? (
|
||||
<SetupTab
|
||||
@@ -87,8 +94,19 @@ const EventType = ({
|
||||
),
|
||||
availability: <></>,
|
||||
team: <></>,
|
||||
advanced: tabs.includes("advanced") ? (
|
||||
<EventAdvancedPlatformWrapper
|
||||
eventType={eventType}
|
||||
team={team}
|
||||
user={user?.data}
|
||||
isUserLoading={isUserLoading}
|
||||
showToast={showToast}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
),
|
||||
|
||||
limits: tabs.includes("limits") ? <EventLimitsTabPlatformWrapper eventType={eventType} /> : <></>,
|
||||
advanced: <></>,
|
||||
instant: <></>,
|
||||
recurring: tabs.includes("recurring") ? (
|
||||
<EventRecurringTabPlatformWrapper eventType={eventType} />
|
||||
|
||||
@@ -250,8 +250,9 @@ const EventTypeWeb = ({
|
||||
<EventAdvancedTab
|
||||
eventType={eventType}
|
||||
team={team}
|
||||
loggedInUser={loggedInUser}
|
||||
isLoggedInUserPending={isLoggedInUserPending}
|
||||
user={loggedInUser}
|
||||
isUserLoading={isLoggedInUserPending}
|
||||
showToast={showToast}
|
||||
/>
|
||||
),
|
||||
instant: <EventInstantTab eventType={eventType} isTeamEvent={!!team} />,
|
||||
|
||||
@@ -18,6 +18,8 @@ export type HorizontalTabItemProps = {
|
||||
linkScroll?: boolean;
|
||||
icon?: IconName;
|
||||
avatar?: string;
|
||||
onClick?: (name: string) => void;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
const HorizontalTabItem = function ({
|
||||
@@ -30,10 +32,16 @@ const HorizontalTabItem = function ({
|
||||
}: HorizontalTabItemProps) {
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
|
||||
const isCurrent = useUrlMatchesCurrentUrl(href);
|
||||
const isCurrent = useUrlMatchesCurrentUrl(href) || props?.isActive;
|
||||
|
||||
return (
|
||||
<Link
|
||||
onClick={(e) => {
|
||||
if (props.onClick) {
|
||||
e.preventDefault();
|
||||
props.onClick(name);
|
||||
}
|
||||
}}
|
||||
key={name}
|
||||
href={href}
|
||||
shallow={linkShallow}
|
||||
|
||||
@@ -26,6 +26,7 @@ export type VerticalTabItemProps = {
|
||||
avatar?: string;
|
||||
iconClassName?: string;
|
||||
onClick?: (name: string) => void;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
||||
const VerticalTabItem = ({
|
||||
@@ -39,7 +40,7 @@ const VerticalTabItem = ({
|
||||
...props
|
||||
}: VerticalTabItemProps) => {
|
||||
const { t } = useLocale();
|
||||
const isCurrent = useUrlMatchesCurrentUrl(href);
|
||||
const isCurrent = useUrlMatchesCurrentUrl(href) || props?.isActive;
|
||||
|
||||
return (
|
||||
<Fragment key={name}>
|
||||
|
||||
Reference in New Issue
Block a user