fix: Bunch of fixes and minor changes throughout the app (#23356)

* Bunch of fixes and minor changes throughout the app

* remove logs

* fix: e2e

* fix: e2e

* fix: more e2e and design

* revert insights upgrade change
This commit is contained in:
Amit Sharma
2025-09-26 12:25:49 +00:00
committed by GitHub
parent 8b7947a172
commit d261a23dbc
25 changed files with 300 additions and 195 deletions
@@ -11,6 +11,7 @@ export const TeamsCTA = () => {
data-testid="new-team-btn"
variant="fab"
StartIcon="plus"
size="sm"
type="button"
href={`${WEBAPP_URL}/settings/teams/new?returnTo=${WEBAPP_URL}/teams`}>
{t("new")}
@@ -438,6 +438,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
name={t("profile")}
href={`/settings/teams/${team.id}/profile`}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="me-5 h-7 w-auto !px-2"
disableChevron
/>
)}
@@ -445,6 +446,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
name={t("members")}
href={`/settings/teams/${team.id}/members`}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="me-5 h-7 w-auto !px-2"
disableChevron
/>
{/* Show roles only for sub-teams with PBAC-enabled parent */}
@@ -465,6 +467,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
name={t("appearance")}
href={`/settings/teams/${team.id}/appearance`}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="me-5 h-7 w-auto !px-2"
disableChevron
/>
{/* Hide if there is a parent ID */}
@@ -474,6 +477,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
name={t("billing")}
href={`/settings/teams/${team.id}/billing`}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="me-5 h-7 w-auto !px-2"
disableChevron
/>
</>
@@ -482,6 +486,7 @@ const TeamListCollapsible = ({ teamFeatures }: { teamFeatures?: Record<number, T
name={t("settings")}
href={`/settings/teams/${team.id}/settings`}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="me-5 h-7 w-auto !px-2"
disableChevron
/>
</>
@@ -643,6 +648,7 @@ const SettingsSidebarContainer = ({
name={t("add_a_team")}
href={`${WEBAPP_URL}/settings/teams/new`}
textClassNames="px-3 items-center mt-2 text-emphasis font-medium text-sm"
className="me-5 h-7 w-auto !px-2"
icon="plus"
disableChevron
/>
@@ -744,12 +750,14 @@ const SettingsSidebarContainer = ({
name={t("profile")}
href={`/settings/organizations/teams/other/${otherTeam.id}/profile`}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="me-5 h-7 w-auto !px-2"
disableChevron
/>
<VerticalTabItem
name={t("members")}
href={`/settings/organizations/teams/other/${otherTeam.id}/members`}
textClassNames="px-3 text-emphasis font-medium text-sm"
className="me-5 h-7 w-auto !px-2"
disableChevron
/>
<>
@@ -81,7 +81,7 @@ describe("EditLocationDialog", () => {
},
{
value: "integrations:daily",
label: "Cal Video (Global)",
label: "Cal Video (Default)",
disabled: false,
icon: "/app-store/dailyvideo/icon.svg",
slug: "daily-video",
+3 -2
View File
@@ -426,6 +426,7 @@ export default function Signup({
id="signup-email"
{...register("email")}
label={t("email")}
placeholder="john@doe.com"
type="email"
autoComplete="email"
disabled={prepopulateFormValues?.email}
@@ -522,8 +523,8 @@ export default function Signup({
usernameTaken
}>
{premiumUsername && !usernameTaken
? `${t("create_account")} (${getPremiumPlanPriceValue()})`
: t("create_account")}
? `${t("get_started")} (${getPremiumPlanPriceValue()})`
: t("get_started")}
</Button>
)}
</Form>
+11 -2
View File
@@ -239,7 +239,7 @@ test.describe("Event Types tests", () => {
await gotoFirstEventType(page);
await page.getByTestId("location-select").click();
await page.locator(`text="Cal Video (Global)"`).click();
await page.locator(`text="Cal Video (Default)"`).click();
await saveEventType(page);
await gotoBookingPage(page);
@@ -282,7 +282,7 @@ test.describe("Event Types tests", () => {
await selectAttendeePhoneNumber(page);
// Add Cal Video location
await addAnotherLocation(page, "Cal Video (Global)");
await addAnotherLocation(page, "Cal Video (Default)");
await saveEventType(page);
@@ -496,6 +496,9 @@ test.describe("Event Types tests", () => {
});
await test.step("should open first eventType and check Interface Language", async () => {
await gotoFirstEventType(page);
// Go to Advanced tab
await page.click("[data-testid=vertical-tab-event_advanced_tab_title]");
await page.click("[data-testid=event-interface-language-toggle]");
const interfaceLanguageValue = page
.getByTestId("event-interface-language")
.locator('div[class$="-singleValue"]');
@@ -518,6 +521,9 @@ test.describe("Event Types tests", () => {
await test.step("should open first eventType and change Interface Language to Deutsche", async () => {
await gotoFirstEventType(page);
// Go to Advanced tab and enable offerSeats
await page.click("[data-testid=vertical-tab-event_advanced_tab_title]");
await page.click("[data-testid=event-interface-language-toggle]");
await page.getByTestId("event-interface-language").click();
await page.locator(`text="Deutsch"`).click();
await saveEventType(page);
@@ -574,6 +580,9 @@ test.describe("Event Types tests", () => {
await page.goto("/event-types");
await page.waitForSelector('[data-testid="event-types"]');
await gotoFirstEventType(page);
// Go to Advanced tab and enable offerSeats
await page.click("[data-testid=vertical-tab-event_advanced_tab_title]");
await page.click("[data-testid=event-interface-language-toggle]");
await page.getByTestId("event-interface-language").click();
await page.getByTestId("select-option-es").click();
await saveEventType(page);
+1 -1
View File
@@ -198,7 +198,7 @@ export async function expectSegmentCleared(page: Page) {
// Check that no segment is selected (button shows default text)
const segmentSelect = page.getByTestId("filter-segment-select");
const buttonText = await segmentSelect.textContent();
expect(buttonText?.trim()).toEqual("Segment");
expect(buttonText?.trim()).toEqual("Saved filters");
}
/**
@@ -199,6 +199,7 @@ export function createBookingPageFixture(page: Page) {
await expect(eventTypePage.getByTestId("time")).toHaveCount(count);
},
getAITranslationToggleDisabled: async () => {
await page.click("[data-testid=vertical-tab-event_advanced_tab_title]");
await page.waitForSelector('[data-testid="ai_translation_toggle"]', {
timeout: 5000,
state: "attached",
@@ -207,6 +208,7 @@ export function createBookingPageFixture(page: Page) {
return (await toggle.getAttribute("disabled")) !== null;
},
toggleAITranslation: async () => {
await page.click("[data-testid=vertical-tab-event_advanced_tab_title]");
await page.getByTestId("ai_translation_toggle").click();
},
updateEventTypeDescription: async (description: string) => {
@@ -81,10 +81,13 @@ test.describe("Managed Event Types", () => {
await page.goto(`/event-types/${managedEvent.id}?tabName=setup`);
await expect(page.getByTestId("vertical-tab-basics")).toHaveAttribute("aria-current", "page"); // fix the race condition
await expect(page.getByTestId("vertical-tab-basics")).toContainText("Basics"); //fix the race condition
await page.locator("#location-select").click();
const optionText = await getByKey(page, "organizer_default_conferencing_app");
await expect(optionText).toBeVisible();
await optionText.click();
const targetText = (await localize("en"))("organizer_default_conferencing_app");
const options = page.locator('[data-testid="location-select-item-conferencing"]');
const targetOption = options.locator(`text=${targetText}`);
await expect(targetOption).toBeVisible();
await targetOption.click();
await saveAndWaitForResponse(page);
await page.getByTestId("vertical-tab-assignment").click();
@@ -190,7 +193,7 @@ test.describe("Managed Event Types", () => {
});
const MANAGED_EVENT_TABS: { slug: string; locator: (page: Page) => Locator | Promise<Locator> }[] = [
{ slug: "setup", locator: (page) => getByKey(page, "translate_description_button") },
{ slug: "setup", locator: (page) => getByKey(page, "allow_multiple_durations") },
{
slug: "team",
locator: (page) => getByKey(page, "automatically_add_all_team_members"),
+13 -7
View File
@@ -321,6 +321,7 @@
"secret_meeting": "Secret Meeting",
"login_instead": "Login instead",
"create_account": "Create Account",
"get_started": "Get started",
"confirm_password": "Confirm password",
"reset_your_password": "Set your new password with the instructions sent to your email address.",
"org_banner_instructions": "Please upload image of {{width}} width and {{height}} height.",
@@ -958,7 +959,7 @@
"light_event_type_color": "Event Type Color (Light Theme)",
"dark_event_type_color": "Event Type Color (Dark Theme)",
"file_not_named": "File is not named [idOrSlug]/[user]",
"create_team": "Create Team",
"create_team": "Create team",
"name": "Name",
"nameless_team": "Nameless Team",
"oauth_clients": "OAuth Clients",
@@ -1019,6 +1020,7 @@
"attendees": "Attendees",
"add_attendees": "Add attendees",
"show_advanced_settings": "Show advanced settings",
"hide_advanced_settings": "Hide advanced settings",
"event_name": "Event Name",
"event_name_in_calendar": "Calendar event name",
"event_name_tooltip": "The name that will appear in calendars",
@@ -1231,7 +1233,7 @@
"contact_sales": "Contact Sales",
"error_404": "Error 404",
"default": "Default",
"set_to_default": "Set to Default",
"set_to_default": "Set as Default",
"new_schedule_btn": "New schedule",
"add_new_schedule": "Add a new schedule",
"add_new_calendar": "Add a new calendar",
@@ -1329,7 +1331,7 @@
"make_team_private_description": "Your team members won't be able to see other team members when this is turned on.",
"you_cannot_see_team_members": "You cannot see all the team members of a private team.",
"you_cannot_see_teams_of_org": "You cannot see teams of a private organization.",
"allow_booker_to_select_duration": "Allow booker to select from multiple durations",
"allow_multiple_durations": "Allow multiple durations",
"impersonate_user_tip": "All uses of this feature is audited.",
"impersonating_user_warning": "Impersonating username \"{{user}}\".",
"impersonating_stop_instructions": "Click here to stop",
@@ -2689,7 +2691,7 @@
"enterprise": "Enterprise",
"enterprise_description": "Upgrade to Enterprise to create your Organization",
"create_your_org": "Create your Organization",
"create_org": "Create Organization",
"create_org": "Create organization",
"create_your_org_description": "Upgrade to Organizations and receive a subdomain, unified billing, Insights, extensive whitelabeling and more",
"create_your_enterprise_description": "Upgrade to Enterprise and get access to Active Directory Sync, SCIM Automatic User provisioning, Cal.ai Voice Agents, Admin APIs and more!",
"other_payment_app_enabled": "You can only enable one payment app per event type",
@@ -2923,6 +2925,7 @@
"proceeding_agreement": "By proceeding, you agree to our",
"terms": "Terms",
"always_show_x_days": "Always {{x}} days available",
"always_show_x_days_description": "Automatically moves the {{x}}-day booking window forward each day, ensuring your calendar always shows the next {{x}} days of available slots.",
"team_subscription_cancelled_successfully": "Team subscription cancelled successfully",
"team_subscription_cancellation_error": "Error while cancelling the team subscription",
"unable_to_subscribe_to_the_platform": "An error occurred while trying to subscribe to the platform plan, please try again later",
@@ -3144,7 +3147,8 @@
"filter_operator_ends_with": "Ends with",
"filter_operator_is_empty": "Is empty",
"filter_operator_not_empty": "Not empty",
"translate_description_button": "Translate title/description to the visitor's browser language using AI",
"translate_description_button": "Auto translate title and description",
"translate_description_button_description": "Automatically translate titles and descriptions to the visitors browser language using AI.",
"rr_distribution_method": "Distribution",
"rr_distribution_method_description": "Allows for optimising distribution for maximum availability or to aim for a more balanced assignment.",
"rr_distribution_method_availability_title": "Maximize availability",
@@ -3310,6 +3314,7 @@
"seats_doesnt_support_recurring": "Seats feature doesn't support recurring event. Disable seats feature to make it recurring.",
"recurring_event_seats_error": "Recurring event doesn't support seats feature. Disable seats feature or make the event non-recurring.",
"segment": "Segment",
"saved_filters": "Saved filters",
"rename": "Rename",
"rename_segment": "Rename Segment",
"delete_segment": "Delete Segment",
@@ -3339,6 +3344,7 @@
"picklist": "Picklist",
"most_cancelled_bookings": "Most Cancelled",
"interface_language": "Interface Language",
"interface_language_description": "Set your preferred language for the booking interface",
"visitors_browser_language": "Visitor's browser language",
"name_or_email": "Name or Email",
"salesforce_round_robin_skip_fallback_to_lead_owner": "If no contact is found, fallback to lead owner if it exists",
@@ -3571,8 +3577,8 @@
"rr_load_balancing_disabled_with_groups": "Load Balancing is not available with Round Robin Groups",
"round_robin_settings_description": "Customize the default round robin settings for this team",
"round_robin_settings_updated_successfully": "Round Robin settings updated successfully",
"booking_creation_time": "Booking Creation Time",
"meeting_start_time": "Meeting Start Time",
"booking_creation_time": "Booking creation time",
"meeting_start_time": "Meeting start time",
"members_affected_by_disabling_delegation_credential": "Affected members",
"no_members_affected_by_disabling_delegation_credential": "No members affected by disabling delegation credential",
"download_expense_log": "Download Expense Log",
+1 -1
View File
@@ -68,7 +68,7 @@ function getApps(credentials: CredentialDataWithTeamName[], filterOnCredentials?
delegatedToId: null,
delegationCredentialId: null,
team: {
name: "Global",
name: "Default",
},
};
logger.debug(
@@ -123,7 +123,7 @@ function CategoryTab({ selectedCategory, categories, searchText, onCategoryChang
selectedCategory === cat ? "bg-emphasis text-default" : "bg-muted text-emphasis",
"hover:bg-emphasis rounded-md px-4 py-2.5 text-sm font-medium transition hover:cursor-pointer"
)}>
{cat[0].toUpperCase() + cat.slice(1)}
{cat === "crm" ? cat.toUpperCase() : cat[0].toUpperCase() + cat.slice(1)}
</li>
))}
</ul>
@@ -19,6 +19,7 @@ import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import type { ButtonProps } from "@calcom/ui/components/button";
import { showToast } from "@calcom/ui/components/toast";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
interface AppCardProps {
app: App;
@@ -119,14 +120,14 @@ export function AppCard({ app, credentials, searchText, userAdminTeams }: AppCar
</div> */}
<p
className="text-default mt-2 flex-grow text-sm"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: markdownToSafeHTML(app.description) }}
style={{
overflow: "hidden",
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: "3",
}}>
{app.description}
</p>
}}/>
<div className="mt-5 flex max-w-full flex-row justify-between gap-2">
<Button
@@ -74,15 +74,15 @@ function ColumnVisibilityButtonComponent<TData>(
})}
</CommandGroup>
</CommandList>
<CommandSeparator />
<CommandSeparator className="mb-0" />
<CommandGroup>
<CommandItem
onSelect={() => {
allColumns.forEach((column) => column.toggleVisibility(true));
}}
className={classNames(
"w-full justify-center text-center",
buttonClasses({ color: "secondary" })
buttonClasses({ color: "secondary" }),
"w-full justify-center text-center cursor-pointer mt-0"
)}>
{t("show_all_columns")}
</CommandItem>
@@ -155,11 +155,11 @@ export function FilterSegmentSelect() {
StartIcon="list-filter"
EndIcon="chevron-down"
data-testid="filter-segment-select">
{selectedSegment?.name || t("segment")}
{selectedSegment?.name || t("saved_filters")}
</Button>
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent align="start" className="w-60" data-testid="filter-segment-select-content">
<DropdownMenuContent align="end" className="w-60" data-testid="filter-segment-select-content">
{segmentGroups.length === 0 && <p className="text-subtle px-3 py-1">{t("no_segments")}</p>}
{segmentGroups.map((group, index) => (
@@ -45,7 +45,7 @@ const MakeTeamPrivateSwitch = ({
setTeamPrivate(checked);
mutation.mutate({ id: teamId, isPrivate: checked });
}}
switchContainerClassName="my-6"
switchContainerClassName="mt-6"
data-testid="make-team-private-check"
/>
</>
@@ -229,7 +229,7 @@ export default function WorkflowListPage({ workflows }: Props) {
<div>
<div className="hidden md:block">
{workflow.team?.name && (
<Badge className="mr-4 mt-1 p-[1px] px-2" variant="gray">
<Badge className="mr-4 mt-1 mb-2 p-[1px] px-2" variant="gray">
<Avatar
alt={workflow.team?.name || ""}
href={
@@ -11,132 +11,159 @@ import { TextField } from "@calcom/ui/components/form";
import { SettingsToggle } from "@calcom/ui/components/form";
import LocationSettingsContainer from "./LocationSettingsContainer";
import { Tooltip } from "@calcom/ui/components/tooltip";
import { Icon } from "@calcom/ui/components/icon";
import { useState } from "react";
import { useAutoAnimate } from "@formkit/auto-animate/react";
const CalVideoSettings = ({ calVideoSettings }: { calVideoSettings?: CalVideoSettingsType }) => {
const { t } = useLocale();
const formMethods = useFormContext<FormValues>();
const isPlatform = useIsPlatform();
const [isExpanded, setIsExpanded] = useState(false);
const [parent] = useAutoAnimate<HTMLDivElement>();
return (
<LocationSettingsContainer>
<Controller
name="calVideoSettings.disableRecordingForGuests"
defaultValue={!!calVideoSettings?.disableRecordingForGuests}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("disable_recording_for_guests")}
labelClassName="text-sm leading-6 whitespace-normal break-words"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
<>
<Tooltip content="expandable" side="right" className="lg:hidden">
<button
aria-expanded={isExpanded}
onClick={() => setIsExpanded(!isExpanded)}
className={classNames(
"todesktop:py-[7px] text-default group flex w-full items-center rounded-md px-2 py-1.5 text-sm font-medium transition",
"[&[aria-current='page']]:!bg-transparent",
"[&[aria-current='page']]:text-emphasis mt-0.5 text-sm",
)}>
<span className="hidden w-full justify-between truncate text-ellipsis lg:flex">
{!isExpanded ? t("show_advanced_settings") : t("hide_advanced_settings")}
</span>
<Icon name={isExpanded ? "chevron-up" : "chevron-down"} className="ml-auto h-4 w-4" />
</button>
</Tooltip>
<div ref={parent}>
{isExpanded && (
<LocationSettingsContainer>
<Controller
name="calVideoSettings.disableRecordingForGuests"
defaultValue={!!calVideoSettings?.disableRecordingForGuests}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("disable_recording_for_guests")}
labelClassName="text-sm leading-6 whitespace-normal break-words"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
/>
);
}}
/>
);
}}
/>
<Controller
name="calVideoSettings.disableRecordingForOrganizer"
defaultValue={!!calVideoSettings?.disableRecordingForOrganizer}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("disable_recording_for_organizer")}
labelClassName="text-sm leading-6 whitespace-normal break-words"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
<Controller
name="calVideoSettings.disableRecordingForOrganizer"
defaultValue={!!calVideoSettings?.disableRecordingForOrganizer}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("disable_recording_for_organizer")}
labelClassName="text-sm leading-6 whitespace-normal break-words"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
/>
);
}}
/>
);
}}
/>
{!isPlatform && (
<Controller
name="calVideoSettings.enableAutomaticRecordingForOrganizer"
defaultValue={!!calVideoSettings?.enableAutomaticRecordingForOrganizer}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("enable_automatic_recording")}
labelClassName="text-sm"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
{!isPlatform && (
<Controller
name="calVideoSettings.enableAutomaticRecordingForOrganizer"
defaultValue={!!calVideoSettings?.enableAutomaticRecordingForOrganizer}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("enable_automatic_recording")}
labelClassName="text-sm"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
/>
);
}}
/>
);
}}
/>
)}
)}
<Controller
name="calVideoSettings.enableAutomaticTranscription"
defaultValue={!!calVideoSettings?.enableAutomaticTranscription}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("enable_automatic_transcription")}
labelClassName="text-sm leading-6 whitespace-normal break-words"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
<Controller
name="calVideoSettings.enableAutomaticTranscription"
defaultValue={!!calVideoSettings?.enableAutomaticTranscription}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("enable_automatic_transcription")}
labelClassName="text-sm leading-6 whitespace-normal break-words"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
/>
);
}}
/>
);
}}
/>
{!isPlatform && (
<Controller
name="calVideoSettings.disableTranscriptionForGuests"
defaultValue={!!calVideoSettings?.disableTranscriptionForGuests}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("disable_transcription_for_guests")}
labelClassName="text-sm leading-6 whitespace-normal break-words"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
{!isPlatform && (
<Controller
name="calVideoSettings.disableTranscriptionForGuests"
defaultValue={!!calVideoSettings?.disableTranscriptionForGuests}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("disable_transcription_for_guests")}
labelClassName="text-sm leading-6 whitespace-normal break-words"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
/>
);
}}
/>
);
}}
/>
)}
{!isPlatform && (
<Controller
name="calVideoSettings.disableTranscriptionForOrganizer"
defaultValue={!!calVideoSettings?.disableTranscriptionForOrganizer}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("disable_transcription_for_organizer")}
labelClassName="text-sm leading-6 whitespace-normal break-words"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
)}
{!isPlatform && (
<Controller
name="calVideoSettings.disableTranscriptionForOrganizer"
defaultValue={!!calVideoSettings?.disableTranscriptionForOrganizer}
render={({ field: { onChange, value } }) => {
return (
<SettingsToggle
title={t("disable_transcription_for_organizer")}
labelClassName="text-sm leading-6 whitespace-normal break-words"
checked={value}
onCheckedChange={onChange}
Badge={<UpgradeTeamsBadge checkForActiveStatus />}
/>
);
}}
/>
);
}}
/>
)}
)}
<TextField
label={t("enter_redirect_url_on_exit_description")}
defaultValue={calVideoSettings?.redirectUrlOnExit || ""}
data-testid="calVideoSettings.redirectUrlOnExit"
containerClassName="mt-4"
className="leading-6"
{...formMethods.register("calVideoSettings.redirectUrlOnExit", {
setValueAs: (v) => (!v || v.trim() === "" ? null : v),
})}
/>
<ErrorMessage
errors={formMethods.formState.errors?.calVideoSettings}
name="redirectUrlOnExit"
className={classNames("text-error text-sm")}
as="div"
id="calVideoSettings.redirectUrlOnExit-error"
/>
</LocationSettingsContainer>
<TextField
label={t("enter_redirect_url_on_exit_description")}
defaultValue={calVideoSettings?.redirectUrlOnExit || ""}
data-testid="calVideoSettings.redirectUrlOnExit"
containerClassName="mt-4"
className="leading-6"
{...formMethods.register("calVideoSettings.redirectUrlOnExit", {
setValueAs: (v) => (!v || v.trim() === "" ? null : v),
})}
/>
<ErrorMessage
errors={formMethods.formState.errors?.calVideoSettings}
name="redirectUrlOnExit"
className={classNames("text-error text-sm")}
as="div"
id="calVideoSettings.redirectUrlOnExit-error"
/>
</LocationSettingsContainer>
)}
</div>
</>
);
};
@@ -60,6 +60,7 @@ import {
CheckboxField,
Switch,
SettingsToggle,
Select,
} from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
@@ -120,6 +121,7 @@ export type EventAdvancedBaseProps = Pick<EventTypeSetupProps, "eventType" | "te
>;
isUserLoading?: boolean;
showToast: (message: string, variant: "success" | "warning" | "error") => void;
orgId: number | null;
customClassNames?: EventAdvancedTabCustomClassNames;
};
@@ -130,6 +132,7 @@ export type EventAdvancedTabProps = EventAdvancedBaseProps & {
error: unknown;
};
showBookerLayoutSelector: boolean;
localeOptions?: { value: string; label: string }[];
verifiedEmails?: string[];
};
@@ -253,6 +256,11 @@ const destinationCalendarComponents = {
/>
</div>
)}
{!showConnectedCalendarSettings && (
<p className="text-emphasis mb-2 block text-sm font-medium leading-none">
{t("add_to_calendar")}
</p>
)}
{!useEventTypeDestinationCalendarEmail &&
verifiedSecondaryEmails.length > 0 &&
!isTeamEventType && (
@@ -411,6 +419,8 @@ export const EventAdvancedTab = ({
showBookerLayoutSelector,
customClassNames,
verifiedEmails,
orgId,
localeOptions,
}: EventAdvancedTabProps) => {
const isPlatform = useIsPlatform();
const platformContext = useAtomsContext();
@@ -423,6 +433,9 @@ export const EventAdvancedTab = ({
!!formMethods.getValues("multiplePrivateLinks") &&
formMethods.getValues("multiplePrivateLinks")?.length !== 0
);
const [interfaceLanguageVisible, setInterfaceLanguageVisible] = useState(
!!formMethods.getValues("interfaceLanguage")
);
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!formMethods.getValues("successRedirectUrl"));
const bookingFields: Prisma.JsonObject = {};
@@ -457,6 +470,10 @@ export const EventAdvancedTab = ({
formMethods.getValues("metadata")?.apps?.stripe?.paymentOption === "HOLD";
const isRecurringEvent = !!formMethods.getValues("recurringEvent");
const interfaceLanguageOptions =
localeOptions && localeOptions.length > 0
? [{ label: t("visitors_browser_language"), value: "" }, ...localeOptions]
: [];
const isRoundRobinEventType =
eventType.schedulingType && eventType.schedulingType === SchedulingType.ROUND_ROBIN;
@@ -481,11 +498,12 @@ export const EventAdvancedTab = ({
);
};
const { isChildrenManagedEventType, isManagedEventType, shouldLockDisableProps } = useLockedFieldsManager({
eventType,
translate: t,
formMethods,
});
const { isChildrenManagedEventType, isManagedEventType, shouldLockDisableProps, shouldLockIndicator } =
useLockedFieldsManager({
eventType,
translate: t,
formMethods,
});
const eventNamePlaceholder = getEventName({
...eventNameObject,
eventName: formMethods.watch("eventName"),
@@ -726,6 +744,69 @@ export const EventAdvancedTab = ({
/>
)}
/>
{!isPlatform && (
<Controller
name="autoTranslateDescriptionEnabled"
render={({ field: { value, onChange } }) => (
<SettingsToggle
labelClassName="text-sm"
title={t("translate_description_button")}
checked={value}
onCheckedChange={(e) => onChange(e)}
disabled={!orgId}
tooltip={!orgId ? t("orgs_upgrade_to_enable_feature") : undefined}
data-testid="ai_translation_toggle"
toggleSwitchAtTheEnd={true}
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
description={t("translate_description_button_description")}
/>
)}
/>
)}
{!isPlatform && (
<Controller
name="interfaceLanguage"
control={formMethods.control}
defaultValue={eventType.interfaceLanguage ?? ""}
render={({ field: { value, onChange } }) => (
<SettingsToggle
labelClassName="text-sm"
toggleSwitchAtTheEnd={true}
switchContainerClassName={classNames(
"border-subtle rounded-lg border py-6 px-4 sm:px-6",
interfaceLanguageVisible && "rounded-b-none"
)}
childrenClassName="lg:ml-0"
data-testid="event-interface-language-toggle"
title={t("interface_language")}
description={t("interface_language_description")}
checked={interfaceLanguageVisible}
{...shouldLockIndicator("interfaceLanguage")}
onCheckedChange={(e) => {
setInterfaceLanguageVisible(e);
if (!e) {
// disables the setting
formMethods.setValue("interfaceLanguage", null, { shouldDirty: true });
} else {
// "" is default value which means visitors browser language
formMethods.setValue("interfaceLanguage", "", { shouldDirty: true });
}
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
<Select<{ label: string; value: string }>
data-testid="event-interface-language"
className="capitalize"
options={interfaceLanguageOptions}
onChange={(option) => {
onChange(option?.value);
}}
value={interfaceLanguageOptions.find((option) => option.value === value)}
/>
</div>
</SettingsToggle>
)}
/>
)}
<Controller
name="requiresBookerEmailVerification"
render={({ field: { value, onChange } }) => (
@@ -31,6 +31,8 @@ import {
} from "@calcom/ui/components/form";
import MaxActiveBookingsPerBookerController from "./MaxActiveBookingsPerBookerController";
import { Tooltip } from "@calcom/ui/components/tooltip";
import { Icon } from "@calcom/ui/components/icon";
type IPeriodType = (typeof PeriodType)[keyof typeof PeriodType];
@@ -248,6 +250,7 @@ function RollingLimitRadioItem({
<span className="me-2 ms-2">&nbsp;{t("into_the_future")}</span>
</div>
<div className="-ml-6 flex flex-col py-2">
<div className="flex items-center">
<CheckboxField
checked={!!rollingExcludeUnavailableDays}
disabled={isDisabled}
@@ -270,6 +273,15 @@ function RollingLimitRadioItem({
);
}}
/>
<Tooltip content={t("always_show_x_days_description", {
x: periodDaysWatch,
})}>
<Icon
name="info"
className="text-default hover:text-attention hover:bg-attention ms-1 inline h-4 w-4 rounded-md"
/>
</Tooltip>
</div>
</div>
</div>
</div>
@@ -72,18 +72,12 @@ export const EventSetupTab = (
const formMethods = useFormContext<FormValues>();
const { eventType, team, urlPrefix, hasOrgBranding, customClassNames, orgId } = props;
const interfaceLanguageOptions =
props.localeOptions && props.localeOptions.length > 0
? [{ label: t("visitors_browser_language"), value: "" }, ...props.localeOptions]
: [];
const [multipleDuration, setMultipleDuration] = useState(
formMethods.getValues("metadata")?.multipleDuration
);
const [firstRender, setFirstRender] = useState(true);
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
const autoTranslateDescriptionEnabled = formMethods.watch("autoTranslateDescriptionEnabled");
const multipleDurationOptions = [
5, 10, 15, 20, 25, 30, 45, 50, 60, 75, 80, 90, 120, 150, 180, 240, 300, 360, 420, 480,
@@ -161,48 +155,6 @@ export const EventSetupTab = (
</>
)}
</div>
{!isPlatform && (
<div className="[&_label]:my-1 [&_label]:font-normal">
<SettingsToggle
title={t("translate_description_button")}
checked={!!autoTranslateDescriptionEnabled}
onCheckedChange={(value) => {
formMethods.setValue("autoTranslateDescriptionEnabled", value, { shouldDirty: true });
}}
disabled={!orgId}
tooltip={!orgId ? t("orgs_upgrade_to_enable_feature") : undefined}
data-testid="ai_translation_toggle"
/>
</div>
)}
{!isPlatform && interfaceLanguageOptions.length > 0 && (
<div>
<Skeleton
as={Label}
loadingClassName="w-16"
htmlFor="interfaceLanguage"
className={customClassNames?.locationSection?.label}>
{t("interface_language")}
{shouldLockIndicator("interfaceLanguage")}
</Skeleton>
<Controller
name="interfaceLanguage"
control={formMethods.control}
defaultValue={eventType.interfaceLanguage ?? ""}
render={({ field: { value, onChange } }) => (
<Select<{ label: string; value: string }>
data-testid="event-interface-language"
className="capitalize"
options={interfaceLanguageOptions}
onChange={(option) => {
onChange(option?.value);
}}
value={interfaceLanguageOptions.find((option) => option.value === value)}
/>
)}
/>
</div>
)}
<TextField
required
label={isPlatform ? "Slug" : t("URL")}
@@ -362,7 +314,7 @@ export const EventSetupTab = (
{!lengthLockedProps.disabled && (
<div className="!mt-4 [&_label]:my-1 [&_label]:font-normal">
<SettingsToggle
title={t("allow_booker_to_select_duration")}
title={t("allow_multiple_durations")}
checked={multipleDuration !== undefined}
disabled={seatsEnabled}
tooltip={seatsEnabled ? t("seat_options_doesnt_multiple_durations") : undefined}
@@ -953,7 +953,6 @@ function VariantFields({
onCheckedChange={(checked) => {
fieldForm.setValue("variant", checked ? otherVariant : defaultVariant);
}}
classNames={{ container: "mt-2" }}
tooltip={t("Toggle Variant")}
/>
) : (
@@ -51,7 +51,7 @@ export function ScheduleListItem({
<div className="space-x-2 rtl:space-x-reverse">
<span className="text-emphasis truncate font-medium">{schedule.name}</span>
{schedule.isDefault && (
<Badge variant="success" className="text-xs">
<Badge variant="gray" className="text-xs">
{t("default")}
</Badge>
)}
@@ -1,6 +1,7 @@
import type { EventAdvancedBaseProps } from "@calcom/features/eventtypes/components/tabs/advanced/EventAdvancedTab";
import { EventAdvancedTab } from "@calcom/features/eventtypes/components/tabs/advanced/EventAdvancedTab";
import { trpc } from "@calcom/trpc/react";
import { localeOptions } from "@calcom/lib/i18n";
const EventAdvancedWebWrapper = ({ ...props }: EventAdvancedBaseProps) => {
const connectedCalendarsQuery = trpc.viewer.calendars.connectedCalendars.useQuery();
@@ -17,6 +18,7 @@ const EventAdvancedWebWrapper = ({ ...props }: EventAdvancedBaseProps) => {
}}
showBookerLayoutSelector={true}
verifiedEmails={verifiedEmails}
localeOptions={localeOptions}
/>
);
};
@@ -282,6 +282,7 @@ const EventTypeWeb = ({
user={user}
isUserLoading={isLoggedInUserPending}
showToast={showToast}
orgId={orgBranding?.id ?? null}
/>
),
instant: <EventInstantTab eventType={eventType} isTeamEvent={!!team} />,
@@ -62,7 +62,7 @@ const VerticalTabItem = ({
target={props.isExternalLink ? "_blank" : "_self"}
className={classNames(
props.textClassNames || "text-default text-sm font-medium leading-none",
"hover:bg-subtle [&[aria-current='page']]:bg-subtle [&[aria-current='page']]:text-emphasis group-hover:text-default group flex w-full flex-row items-center rounded-md p-2 transition",
"hover:bg-subtle [&[aria-current='page']]:bg-subtle [&[aria-current='page']]:text-emphasis group-hover:text-default group flex w-full flex-row items-center rounded-md p-2 transition ",
props.disabled && "pointer-events-none !opacity-30",
(isChild || !props.icon) && "ml-7",
props.className