feat: companion add missing advanced event type (#26594)
* feat: companion add missing advanced event type * feat: companion add missing advanced event type * remove metadata code and remove non implemented Auto translate title and description toggle from companion * Save Button Dirty State * address cubic feedback - default value for sendTranscriptionEmails * address cubic comments - fix new event type blocked issue * ios language dropdown
This commit is contained in:
1 parent
51639e38d7
commit
d865f6cb5f
10 files changed
+623
-253
No files matched your search
@@ -32,6 +32,7 @@ yarn-error.*
|
||||
# macOS
|
||||
.DS_Store
|
||||
*.pem
|
||||
safari-extension/
|
||||
|
||||
# local env files
|
||||
.env*.local
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Stack } from "expo-router";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
export default function AvailabilityLayout() {
|
||||
return (
|
||||
|
||||
@@ -107,12 +107,7 @@ export default function AvailabilityDetailIOS() {
|
||||
</Stack.Header.Right>
|
||||
</Stack.Header>
|
||||
|
||||
<AvailabilityDetailScreen
|
||||
ref={screenRef}
|
||||
id={id}
|
||||
// @ts-expect-error - onActionsReady is only available in AvailabilityDetailScreen.ios.tsx
|
||||
onActionsReady={handleActionsReady}
|
||||
/>
|
||||
<AvailabilityDetailScreen ref={screenRef} id={id} onActionsReady={handleActionsReady} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import * as Clipboard from "expo-clipboard";
|
||||
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
|
||||
import { Activity, useCallback, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
Alert,
|
||||
Animated,
|
||||
@@ -49,10 +49,11 @@ import { isLiquidGlassAvailable } from "expo-glass-effect";
|
||||
// Type definitions for extended EventType fields not in the base type
|
||||
interface EventTypeExtended {
|
||||
lengthInMinutesOptions?: number[];
|
||||
disableCancelling?: boolean;
|
||||
disableRescheduling?: boolean;
|
||||
// API V2 format - these can be objects or booleans from older data
|
||||
disableCancelling?: boolean | { disabled: boolean };
|
||||
disableRescheduling?: boolean | { disabled: boolean; minutesBefore?: number };
|
||||
sendCalVideoTranscription?: boolean;
|
||||
autoTranslate?: boolean;
|
||||
|
||||
lockedTimeZone?: string;
|
||||
hideCalendarEventDetails?: boolean;
|
||||
hideOrganizerEmail?: boolean;
|
||||
@@ -60,6 +61,12 @@ interface EventTypeExtended {
|
||||
lightThemeHex?: string;
|
||||
darkThemeHex?: string;
|
||||
};
|
||||
// API V2 new fields
|
||||
calVideoSettings?: {
|
||||
sendTranscriptionEmails?: boolean;
|
||||
};
|
||||
interfaceLanguage?: string;
|
||||
showOptimizedSlots?: boolean;
|
||||
}
|
||||
|
||||
interface BookerActiveBookingsLimitExtended {
|
||||
@@ -199,8 +206,8 @@ export default function EventTypeDetail() {
|
||||
const [requiresConfirmation, setRequiresConfirmation] = useState(false);
|
||||
const [disableCancelling, setDisableCancelling] = useState(false);
|
||||
const [disableRescheduling, setDisableRescheduling] = useState(false);
|
||||
const [sendCalVideoTranscription, setSendCalVideoTranscription] = useState(false);
|
||||
const [autoTranslate, setAutoTranslate] = useState(false);
|
||||
const [sendCalVideoTranscription, setSendCalVideoTranscription] = useState(true);
|
||||
|
||||
const [requiresBookerEmailVerification, setRequiresBookerEmailVerification] = useState(false);
|
||||
const [hideCalendarNotes, setHideCalendarNotes] = useState(false);
|
||||
const [hideCalendarEventDetails, setHideCalendarEventDetails] = useState(false);
|
||||
@@ -215,6 +222,8 @@ export default function EventTypeDetail() {
|
||||
const [customReplyToEmail, setCustomReplyToEmail] = useState("");
|
||||
const [eventTypeColorLight, setEventTypeColorLight] = useState("#292929");
|
||||
const [eventTypeColorDark, setEventTypeColorDark] = useState("#FAFAFA");
|
||||
const [interfaceLanguage, setInterfaceLanguage] = useState("");
|
||||
const [showOptimizedSlots, setShowOptimizedSlots] = useState(false);
|
||||
|
||||
const [seatsEnabled, setSeatsEnabled] = useState(false);
|
||||
const [seatsPerTimeSlot, setSeatsPerTimeSlot] = useState("2");
|
||||
@@ -594,28 +603,55 @@ export default function EventTypeDetail() {
|
||||
|
||||
const metadata = eventType.metadata;
|
||||
|
||||
if (eventTypeExt.disableCancelling !== undefined) {
|
||||
setDisableCancelling(eventTypeExt.disableCancelling);
|
||||
// Handle disableCancelling - can be boolean or object { disabled: boolean }
|
||||
const disableCancellingValue = eventTypeExt.disableCancelling;
|
||||
if (disableCancellingValue !== undefined) {
|
||||
if (typeof disableCancellingValue === "boolean") {
|
||||
setDisableCancelling(disableCancellingValue);
|
||||
} else if (
|
||||
typeof disableCancellingValue === "object" &&
|
||||
"disabled" in disableCancellingValue
|
||||
) {
|
||||
setDisableCancelling(disableCancellingValue.disabled);
|
||||
}
|
||||
} else if (metadata?.disableCancelling) {
|
||||
setDisableCancelling(true);
|
||||
}
|
||||
|
||||
if (eventTypeExt.disableRescheduling !== undefined) {
|
||||
setDisableRescheduling(eventTypeExt.disableRescheduling);
|
||||
// Handle disableRescheduling - can be boolean or object { disabled: boolean, minutesBefore?: number }
|
||||
const disableReschedulingValue = eventTypeExt.disableRescheduling;
|
||||
if (disableReschedulingValue !== undefined) {
|
||||
if (typeof disableReschedulingValue === "boolean") {
|
||||
setDisableRescheduling(disableReschedulingValue);
|
||||
} else if (
|
||||
typeof disableReschedulingValue === "object" &&
|
||||
"disabled" in disableReschedulingValue
|
||||
) {
|
||||
setDisableRescheduling(disableReschedulingValue.disabled);
|
||||
}
|
||||
} else if (metadata?.disableRescheduling) {
|
||||
setDisableRescheduling(true);
|
||||
}
|
||||
|
||||
if (eventTypeExt.sendCalVideoTranscription !== undefined) {
|
||||
// Handle calVideoSettings.sendTranscriptionEmails (API V2) or sendCalVideoTranscription (legacy)
|
||||
if (eventTypeExt.calVideoSettings?.sendTranscriptionEmails !== undefined) {
|
||||
setSendCalVideoTranscription(eventTypeExt.calVideoSettings.sendTranscriptionEmails);
|
||||
} else if (eventTypeExt.sendCalVideoTranscription !== undefined) {
|
||||
setSendCalVideoTranscription(eventTypeExt.sendCalVideoTranscription);
|
||||
} else if (metadata?.sendCalVideoTranscription) {
|
||||
setSendCalVideoTranscription(true);
|
||||
} else {
|
||||
setSendCalVideoTranscription(false);
|
||||
}
|
||||
|
||||
if (eventTypeExt.autoTranslate !== undefined) {
|
||||
setAutoTranslate(eventTypeExt.autoTranslate);
|
||||
} else if (metadata?.autoTranslate) {
|
||||
setAutoTranslate(true);
|
||||
// Load interface language (API V2)
|
||||
if (eventTypeExt.interfaceLanguage !== undefined) {
|
||||
setInterfaceLanguage(eventTypeExt.interfaceLanguage);
|
||||
}
|
||||
|
||||
// Load showOptimizedSlots (API V2)
|
||||
if (eventTypeExt.showOptimizedSlots !== undefined) {
|
||||
setShowOptimizedSlots(eventTypeExt.showOptimizedSlots);
|
||||
}
|
||||
|
||||
if (metadata) {
|
||||
@@ -940,6 +976,171 @@ export default function EventTypeDetail() {
|
||||
]);
|
||||
};
|
||||
|
||||
// Calculate current form state efficiently
|
||||
const currentFormState = useMemo(
|
||||
() => ({
|
||||
// Basics
|
||||
eventTitle,
|
||||
eventSlug,
|
||||
eventDescription,
|
||||
eventDuration,
|
||||
isHidden,
|
||||
locations,
|
||||
disableGuests,
|
||||
|
||||
// Multiple durations
|
||||
allowMultipleDurations,
|
||||
selectedDurations,
|
||||
defaultDuration,
|
||||
|
||||
// Availability
|
||||
selectedScheduleId: selectedSchedule?.id,
|
||||
|
||||
// Limits
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
minimumNoticeValue,
|
||||
minimumNoticeUnit,
|
||||
slotInterval,
|
||||
limitBookingFrequency,
|
||||
frequencyLimits,
|
||||
limitTotalDuration,
|
||||
durationLimits,
|
||||
onlyShowFirstAvailableSlot,
|
||||
maxActiveBookingsPerBooker,
|
||||
maxActiveBookingsValue,
|
||||
offerReschedule,
|
||||
limitFutureBookings,
|
||||
futureBookingType,
|
||||
rollingDays,
|
||||
rollingCalendarDays,
|
||||
rangeStartDate,
|
||||
rangeEndDate,
|
||||
|
||||
// Advanced
|
||||
requiresConfirmation,
|
||||
requiresBookerEmailVerification,
|
||||
hideCalendarNotes,
|
||||
hideCalendarEventDetails,
|
||||
hideOrganizerEmail,
|
||||
lockTimezone,
|
||||
allowReschedulingPastEvents,
|
||||
allowBookingThroughRescheduleLink,
|
||||
successRedirectUrl,
|
||||
forwardParamsSuccessRedirect,
|
||||
customReplyToEmail,
|
||||
eventTypeColorLight,
|
||||
eventTypeColorDark,
|
||||
calendarEventName,
|
||||
addToCalendarEmail,
|
||||
selectedLayouts,
|
||||
defaultLayout,
|
||||
disableCancelling,
|
||||
disableRescheduling,
|
||||
sendCalVideoTranscription,
|
||||
|
||||
interfaceLanguage,
|
||||
showOptimizedSlots,
|
||||
|
||||
// Seats
|
||||
seatsEnabled,
|
||||
seatsPerTimeSlot,
|
||||
showAttendeeInfo,
|
||||
showAvailabilityCount,
|
||||
|
||||
// Recurring
|
||||
recurringEnabled,
|
||||
recurringInterval,
|
||||
recurringFrequency,
|
||||
recurringOccurrences,
|
||||
}),
|
||||
[
|
||||
eventTitle,
|
||||
eventSlug,
|
||||
eventDescription,
|
||||
eventDuration,
|
||||
isHidden,
|
||||
locations,
|
||||
disableGuests,
|
||||
allowMultipleDurations,
|
||||
selectedDurations,
|
||||
defaultDuration,
|
||||
selectedSchedule,
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
minimumNoticeValue,
|
||||
minimumNoticeUnit,
|
||||
slotInterval,
|
||||
limitBookingFrequency,
|
||||
frequencyLimits,
|
||||
limitTotalDuration,
|
||||
durationLimits,
|
||||
onlyShowFirstAvailableSlot,
|
||||
maxActiveBookingsPerBooker,
|
||||
maxActiveBookingsValue,
|
||||
offerReschedule,
|
||||
limitFutureBookings,
|
||||
futureBookingType,
|
||||
rollingDays,
|
||||
rollingCalendarDays,
|
||||
rangeStartDate,
|
||||
rangeEndDate,
|
||||
requiresConfirmation,
|
||||
requiresBookerEmailVerification,
|
||||
hideCalendarNotes,
|
||||
hideCalendarEventDetails,
|
||||
hideOrganizerEmail,
|
||||
lockTimezone,
|
||||
allowReschedulingPastEvents,
|
||||
allowBookingThroughRescheduleLink,
|
||||
successRedirectUrl,
|
||||
forwardParamsSuccessRedirect,
|
||||
customReplyToEmail,
|
||||
eventTypeColorLight,
|
||||
eventTypeColorDark,
|
||||
calendarEventName,
|
||||
addToCalendarEmail,
|
||||
selectedLayouts,
|
||||
defaultLayout,
|
||||
disableCancelling,
|
||||
disableRescheduling,
|
||||
sendCalVideoTranscription,
|
||||
interfaceLanguage,
|
||||
showOptimizedSlots,
|
||||
seatsEnabled,
|
||||
seatsPerTimeSlot,
|
||||
showAttendeeInfo,
|
||||
showAvailabilityCount,
|
||||
recurringEnabled,
|
||||
recurringInterval,
|
||||
recurringFrequency,
|
||||
recurringOccurrences,
|
||||
]
|
||||
);
|
||||
|
||||
// Calculate isDirty state
|
||||
const isDirty = useMemo(() => {
|
||||
// For new event types (id="new"), we always want to enable save if required fields are filled
|
||||
// But for now, let's stick to the dirty check logic which applies mostly to updates
|
||||
if (id === "new") {
|
||||
// For create mode, we can consider it "dirty" if title and slug are present
|
||||
// or just always enabled. The requirement was "when user changes something".
|
||||
// Usually for "new" forms, the button IS enabled, or disabled until valid.
|
||||
// Let's assume the requirement "enable/disable based on changes" specifically targets the EDIT flow.
|
||||
// But to be safe and consistent with "disable until change", we can check if it differs from initial empty state?
|
||||
// Actually, for "Create", it's usually better to be enabled or validated.
|
||||
// However, the user request: "we have this save button in event types, so when user changes something then only i want this save button to enable"
|
||||
// This strongly implies the Edit case.
|
||||
// For "new", returning true (always dirty/ready) is a safe default to avoid blocking creation.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!eventTypeData) return false;
|
||||
|
||||
const payload = buildPartialUpdatePayload(currentFormState, eventTypeData);
|
||||
return Object.keys(payload).length > 0;
|
||||
}, [currentFormState, eventTypeData, id]);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!id) {
|
||||
Alert.alert("Error", "Event type ID is missing");
|
||||
@@ -1017,88 +1218,14 @@ export default function EventTypeDetail() {
|
||||
setSaving(false);
|
||||
} else {
|
||||
// For UPDATE mode, use partial update - only send changed fields
|
||||
const currentFormState = {
|
||||
// Basics
|
||||
eventTitle,
|
||||
eventSlug,
|
||||
eventDescription,
|
||||
eventDuration,
|
||||
isHidden,
|
||||
locations,
|
||||
disableGuests,
|
||||
|
||||
// Multiple durations
|
||||
allowMultipleDurations,
|
||||
selectedDurations,
|
||||
defaultDuration,
|
||||
|
||||
// Availability
|
||||
selectedScheduleId,
|
||||
|
||||
// Limits
|
||||
beforeEventBuffer,
|
||||
afterEventBuffer,
|
||||
minimumNoticeValue,
|
||||
minimumNoticeUnit,
|
||||
slotInterval,
|
||||
limitBookingFrequency,
|
||||
frequencyLimits,
|
||||
limitTotalDuration,
|
||||
durationLimits,
|
||||
onlyShowFirstAvailableSlot,
|
||||
maxActiveBookingsPerBooker,
|
||||
maxActiveBookingsValue,
|
||||
offerReschedule,
|
||||
limitFutureBookings,
|
||||
futureBookingType,
|
||||
rollingDays,
|
||||
rollingCalendarDays,
|
||||
rangeStartDate,
|
||||
rangeEndDate,
|
||||
|
||||
// Advanced
|
||||
requiresConfirmation,
|
||||
requiresBookerEmailVerification,
|
||||
hideCalendarNotes,
|
||||
hideCalendarEventDetails,
|
||||
hideOrganizerEmail,
|
||||
lockTimezone,
|
||||
allowReschedulingPastEvents,
|
||||
allowBookingThroughRescheduleLink,
|
||||
successRedirectUrl,
|
||||
forwardParamsSuccessRedirect,
|
||||
customReplyToEmail,
|
||||
eventTypeColorLight,
|
||||
eventTypeColorDark,
|
||||
calendarEventName,
|
||||
addToCalendarEmail,
|
||||
selectedLayouts,
|
||||
defaultLayout,
|
||||
disableCancelling,
|
||||
disableRescheduling,
|
||||
sendCalVideoTranscription,
|
||||
autoTranslate,
|
||||
|
||||
// Seats
|
||||
seatsEnabled,
|
||||
seatsPerTimeSlot,
|
||||
showAttendeeInfo,
|
||||
showAvailabilityCount,
|
||||
|
||||
// Recurring
|
||||
recurringEnabled,
|
||||
recurringInterval,
|
||||
recurringFrequency,
|
||||
recurringOccurrences,
|
||||
};
|
||||
|
||||
// Build partial payload with only changed fields
|
||||
// Using the memoized currentFormState
|
||||
const payload = buildPartialUpdatePayload(currentFormState, eventTypeData);
|
||||
|
||||
if (Object.keys(payload).length === 0) {
|
||||
Alert.alert("No Changes", "No changes were made to the event type.");
|
||||
setSaving(false);
|
||||
return;
|
||||
// This should theoretically strictly not be reached if button is disabled,
|
||||
// but it acts as a safeguard.
|
||||
// setSaving(false);
|
||||
// return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1179,10 +1306,16 @@ export default function EventTypeDetail() {
|
||||
{/* Save Button */}
|
||||
<AppPressable
|
||||
onPress={handleSave}
|
||||
disabled={saving}
|
||||
className={`px-2 py-2 ${saving ? "opacity-50" : ""}`}
|
||||
disabled={saving || !isDirty}
|
||||
className={`px-2 py-2 ${saving || !isDirty ? "opacity-50" : ""}`}
|
||||
>
|
||||
<Text className="text-[16px] font-semibold text-[#007AFF]">{saveButtonText}</Text>
|
||||
<Text
|
||||
className={`text-[16px] font-semibold ${
|
||||
saving || !isDirty ? "text-[#C7C7CC]" : "text-[#007AFF]"
|
||||
}`}
|
||||
>
|
||||
{saveButtonText}
|
||||
</Text>
|
||||
</AppPressable>
|
||||
</View>
|
||||
</HeaderButtonWrapper>
|
||||
@@ -1238,7 +1371,7 @@ export default function EventTypeDetail() {
|
||||
</Stack.Header.Menu>
|
||||
<Stack.Header.Button
|
||||
onPress={handleSave}
|
||||
disabled={saving}
|
||||
disabled={saving || !isDirty}
|
||||
variant="prominent"
|
||||
tintColor="#000"
|
||||
>
|
||||
@@ -1859,8 +1992,6 @@ export default function EventTypeDetail() {
|
||||
<AdvancedTab
|
||||
requiresConfirmation={requiresConfirmation}
|
||||
setRequiresConfirmation={setRequiresConfirmation}
|
||||
autoTranslate={autoTranslate}
|
||||
setAutoTranslate={setAutoTranslate}
|
||||
requiresBookerEmailVerification={requiresBookerEmailVerification}
|
||||
setRequiresBookerEmailVerification={setRequiresBookerEmailVerification}
|
||||
hideCalendarNotes={hideCalendarNotes}
|
||||
@@ -1898,6 +2029,17 @@ export default function EventTypeDetail() {
|
||||
setShowAvailabilityCount={setShowAvailabilityCount}
|
||||
// Event type ID for private links
|
||||
eventTypeId={id}
|
||||
// New API V2 props
|
||||
disableCancelling={disableCancelling}
|
||||
setDisableCancelling={setDisableCancelling}
|
||||
disableRescheduling={disableRescheduling}
|
||||
setDisableRescheduling={setDisableRescheduling}
|
||||
sendCalVideoTranscription={sendCalVideoTranscription}
|
||||
setSendCalVideoTranscription={setSendCalVideoTranscription}
|
||||
interfaceLanguage={interfaceLanguage}
|
||||
setInterfaceLanguage={setInterfaceLanguage}
|
||||
showOptimizedSlots={showOptimizedSlots}
|
||||
setShowOptimizedSlots={setShowOptimizedSlots}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
|
||||
@@ -1,8 +1,63 @@
|
||||
import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
|
||||
import { Stack } from "expo-router";
|
||||
import { Ionicons } from "@expo/vector-icons";
|
||||
import { Alert, Switch, Text, TextInput, TouchableOpacity, View } from "react-native";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Alert,
|
||||
Modal,
|
||||
Platform,
|
||||
ScrollView,
|
||||
Switch,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from "react-native";
|
||||
|
||||
import { openInAppBrowser } from "@/utils/browser";
|
||||
|
||||
// Interface language options matching API V2 enum
|
||||
const interfaceLanguageOptions = [
|
||||
{ label: "Browser Default", value: "" },
|
||||
{ label: "English", value: "en" },
|
||||
{ label: "العربية", value: "ar" },
|
||||
{ label: "Azərbaycan", value: "az" },
|
||||
{ label: "Български", value: "bg" },
|
||||
{ label: "বাংলা", value: "bn" },
|
||||
{ label: "Català", value: "ca" },
|
||||
{ label: "Čeština", value: "cs" },
|
||||
{ label: "Dansk", value: "da" },
|
||||
{ label: "Deutsch", value: "de" },
|
||||
{ label: "Ελληνικά", value: "el" },
|
||||
{ label: "Español", value: "es" },
|
||||
{ label: "Español latinoamericano", value: "es-419" },
|
||||
{ label: "Eesti", value: "et" },
|
||||
{ label: "Euskara", value: "eu" },
|
||||
{ label: "Suomi", value: "fi" },
|
||||
{ label: "Français", value: "fr" },
|
||||
{ label: "עברית", value: "he" },
|
||||
{ label: "Magyar", value: "hu" },
|
||||
{ label: "Italiano", value: "it" },
|
||||
{ label: "日本語", value: "ja" },
|
||||
{ label: "ខ្មែរ", value: "km" },
|
||||
{ label: "한국어", value: "ko" },
|
||||
{ label: "Nederlands", value: "nl" },
|
||||
{ label: "Norsk", value: "no" },
|
||||
{ label: "Polski", value: "pl" },
|
||||
{ label: "Português", value: "pt" },
|
||||
{ label: "Português (Brasil)", value: "pt-BR" },
|
||||
{ label: "Română", value: "ro" },
|
||||
{ label: "Русский", value: "ru" },
|
||||
{ label: "Slovenčina (Slovensko)", value: "sk-SK" },
|
||||
{ label: "Српски", value: "sr" },
|
||||
{ label: "Svenska", value: "sv" },
|
||||
{ label: "Türkçe", value: "tr" },
|
||||
{ label: "Українська", value: "uk" },
|
||||
{ label: "Tiếng Việt", value: "vi" },
|
||||
{ label: "中文(中国)", value: "zh-CN" },
|
||||
{ label: "中文(台灣)", value: "zh-TW" },
|
||||
];
|
||||
|
||||
interface ConfigureOnWebCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
@@ -43,8 +98,7 @@ function ConfigureOnWebCard({
|
||||
interface AdvancedTabProps {
|
||||
requiresConfirmation: boolean;
|
||||
setRequiresConfirmation: (value: boolean) => void;
|
||||
autoTranslate: boolean;
|
||||
setAutoTranslate: (value: boolean) => void;
|
||||
|
||||
requiresBookerEmailVerification: boolean;
|
||||
setRequiresBookerEmailVerification: (value: boolean) => void;
|
||||
hideCalendarNotes: boolean;
|
||||
@@ -80,9 +134,27 @@ interface AdvancedTabProps {
|
||||
showAvailabilityCount: boolean;
|
||||
setShowAvailabilityCount: (value: boolean) => void;
|
||||
eventTypeId: string;
|
||||
// New API V2 props
|
||||
disableCancelling: boolean;
|
||||
setDisableCancelling: (value: boolean) => void;
|
||||
disableRescheduling: boolean;
|
||||
setDisableRescheduling: (value: boolean) => void;
|
||||
sendCalVideoTranscription: boolean;
|
||||
setSendCalVideoTranscription: (value: boolean) => void;
|
||||
interfaceLanguage: string;
|
||||
setInterfaceLanguage: (value: string) => void;
|
||||
showOptimizedSlots: boolean;
|
||||
setShowOptimizedSlots: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export function AdvancedTab(props: AdvancedTabProps) {
|
||||
const [showLanguagePicker, setShowLanguagePicker] = useState(false);
|
||||
|
||||
const getLanguageLabel = (value: string) => {
|
||||
const option = interfaceLanguageOptions.find((opt) => opt.value === value);
|
||||
return option?.label || "Browser Default";
|
||||
};
|
||||
|
||||
return (
|
||||
<View className="gap-3">
|
||||
<View className="rounded-2xl border border-[#E5E5EA] bg-white p-5">
|
||||
@@ -103,53 +175,192 @@ export function AdvancedTab(props: AdvancedTabProps) {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ConfigureOnWebCard
|
||||
title="Disable Cancelling"
|
||||
description="Guests and Organizer can no longer cancel the event with calendar invite or email."
|
||||
eventTypeId={props.eventTypeId}
|
||||
browserTitle="Disable Cancelling"
|
||||
/>
|
||||
|
||||
<ConfigureOnWebCard
|
||||
title="Disable Rescheduling"
|
||||
description="Guests and Organizer can no longer reschedule the event with calendar invite or email."
|
||||
eventTypeId={props.eventTypeId}
|
||||
browserTitle="Disable Rescheduling"
|
||||
/>
|
||||
|
||||
<ConfigureOnWebCard
|
||||
title="Send Cal Video Transcription Emails"
|
||||
description="Send emails with the transcription of the Cal Video after the meeting ends. (Requires a paid plan)"
|
||||
eventTypeId={props.eventTypeId}
|
||||
browserTitle="Cal Video Transcription"
|
||||
/>
|
||||
|
||||
<View className="rounded-2xl border border-[#E5E5EA] bg-white p-5">
|
||||
<View className="flex-row items-start justify-between">
|
||||
<View className="mr-3 flex-1">
|
||||
<Text className="mb-1 text-base font-medium text-[#333]">
|
||||
Auto translate title and description
|
||||
</Text>
|
||||
<Text className="mb-1 text-base font-medium text-[#333]">Disable Cancelling</Text>
|
||||
<Text className="text-sm text-[#666]">
|
||||
Automatically translate titles and descriptions to the visitor's browser language
|
||||
using AI.
|
||||
Guests and Organizer can no longer cancel the event with calendar invite or email.
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={props.autoTranslate}
|
||||
onValueChange={props.setAutoTranslate}
|
||||
value={props.disableCancelling}
|
||||
onValueChange={props.setDisableCancelling}
|
||||
trackColor={{ false: "#E5E5EA", true: "#34C759" }}
|
||||
thumbColor="#FFFFFF"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ConfigureOnWebCard
|
||||
title="Interface Language"
|
||||
description="Set your preferred language for the booking interface."
|
||||
eventTypeId={props.eventTypeId}
|
||||
browserTitle="Interface Language"
|
||||
/>
|
||||
<View className="rounded-2xl border border-[#E5E5EA] bg-white p-5">
|
||||
<View className="flex-row items-start justify-between">
|
||||
<View className="mr-3 flex-1">
|
||||
<Text className="mb-1 text-base font-medium text-[#333]">Disable Rescheduling</Text>
|
||||
<Text className="text-sm text-[#666]">
|
||||
Guests and Organizer can no longer reschedule the event with calendar invite or email.
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={props.disableRescheduling}
|
||||
onValueChange={props.setDisableRescheduling}
|
||||
trackColor={{ false: "#E5E5EA", true: "#34C759" }}
|
||||
thumbColor="#FFFFFF"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="rounded-2xl border border-[#E5E5EA] bg-white p-5">
|
||||
<View className="flex-row items-start justify-between">
|
||||
<View className="mr-3 flex-1">
|
||||
<Text className="mb-1 text-base font-medium text-[#333]">
|
||||
Send Cal Video Transcription Emails
|
||||
</Text>
|
||||
<Text className="text-sm text-[#666]">
|
||||
Send emails with the transcription of the Cal Video after the meeting ends.
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={props.sendCalVideoTranscription}
|
||||
onValueChange={props.setSendCalVideoTranscription}
|
||||
trackColor={{ false: "#E5E5EA", true: "#34C759" }}
|
||||
thumbColor="#FFFFFF"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className="rounded-2xl border border-[#E5E5EA] bg-white p-5">
|
||||
<Text className="mb-1.5 text-base font-medium text-[#333]">Interface Language</Text>
|
||||
<Text className="mb-3 text-sm text-[#666]">
|
||||
Set your preferred language for the booking interface.
|
||||
</Text>
|
||||
<TouchableOpacity
|
||||
className="flex-row items-center justify-between rounded-lg border border-[#E5E5EA] bg-[#F8F9FA] px-3 py-3"
|
||||
onPress={() => setShowLanguagePicker(true)}
|
||||
>
|
||||
<Text className="text-base text-[#333]">{getLanguageLabel(props.interfaceLanguage)}</Text>
|
||||
<Ionicons name="chevron-down" size={20} color="#666" />
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Language Picker Modal */}
|
||||
<Modal
|
||||
visible={showLanguagePicker}
|
||||
animationType="slide"
|
||||
transparent={Platform.OS !== "ios"}
|
||||
presentationStyle={Platform.OS === "ios" ? "formSheet" : undefined}
|
||||
onRequestClose={() => setShowLanguagePicker(false)}
|
||||
>
|
||||
{Platform.OS === "ios" ? (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
backgroundColor: isLiquidGlassAvailable() ? "transparent" : "#F2F2F7",
|
||||
}}
|
||||
>
|
||||
{/* Glass Header */}
|
||||
{isLiquidGlassAvailable() ? (
|
||||
<GlassView
|
||||
style={{
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
height: 60, // Standard header height
|
||||
zIndex: 10,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
paddingHorizontal: 16,
|
||||
}}
|
||||
glassEffectStyle="regular"
|
||||
>
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowLanguagePicker(false)}
|
||||
className="h-[30px] w-[30px] items-center justify-center rounded-full bg-[#E5E5EA]"
|
||||
>
|
||||
<Ionicons name="close" size={20} color="#8E8E93" />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text className="text-[17px] font-semibold text-black">Select Language</Text>
|
||||
|
||||
{/* Spacer to balance the centered title since we removed the right button */}
|
||||
<View className="h-8 w-8" />
|
||||
</GlassView>
|
||||
) : (
|
||||
<View className="absolute left-0 right-0 top-0 z-10 h-[60px] flex-row items-center justify-between border-b border-[#E5E5EA] bg-white px-4">
|
||||
<TouchableOpacity
|
||||
onPress={() => setShowLanguagePicker(false)}
|
||||
className="h-8 w-8 items-center justify-center rounded-full bg-[#E5E5EA]"
|
||||
>
|
||||
<Ionicons name="close" size={20} color="#8E8E93" />
|
||||
</TouchableOpacity>
|
||||
|
||||
<Text className="text-[17px] font-semibold text-black">Select Language</Text>
|
||||
|
||||
{/* Spacer to balance the centered title */}
|
||||
<View className="h-8 w-8" />
|
||||
</View>
|
||||
)}
|
||||
|
||||
<ScrollView
|
||||
className="flex-1"
|
||||
contentContainerStyle={{ paddingTop: 80, paddingHorizontal: 16, paddingBottom: 40 }}
|
||||
>
|
||||
<View className="overflow-hidden rounded-xl border border-gray-300/40 bg-white/60">
|
||||
{interfaceLanguageOptions.map((option, index) => (
|
||||
<TouchableOpacity
|
||||
key={option.value}
|
||||
className={`flex-row items-center justify-between px-4 py-3 active:bg-white/80 ${
|
||||
index !== interfaceLanguageOptions.length - 1
|
||||
? "border-b border-gray-300/40"
|
||||
: ""
|
||||
}`}
|
||||
onPress={() => {
|
||||
props.setInterfaceLanguage(option.value);
|
||||
setShowLanguagePicker(false);
|
||||
}}
|
||||
>
|
||||
<Text className="text-base text-[#333]">{option.label}</Text>
|
||||
{props.interfaceLanguage === option.value ? (
|
||||
<Ionicons name="checkmark" size={20} color="#007AFF" />
|
||||
) : null}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</ScrollView>
|
||||
</View>
|
||||
) : (
|
||||
<View className="flex-1 justify-end bg-black/50">
|
||||
<View className="max-h-[70%] rounded-t-3xl bg-white">
|
||||
<View className="flex-row items-center justify-between border-b border-[#E5E5EA] p-4">
|
||||
<Text className="text-lg font-semibold text-[#333]">Select Language</Text>
|
||||
<TouchableOpacity onPress={() => setShowLanguagePicker(false)}>
|
||||
<Ionicons name="close" size={24} color="#666" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<ScrollView className="p-2">
|
||||
{interfaceLanguageOptions.map((option) => (
|
||||
<TouchableOpacity
|
||||
key={option.value}
|
||||
className={`flex-row items-center justify-between rounded-lg p-4 ${
|
||||
props.interfaceLanguage === option.value ? "bg-[#F0F0F0]" : ""
|
||||
}`}
|
||||
onPress={() => {
|
||||
props.setInterfaceLanguage(option.value);
|
||||
setShowLanguagePicker(false);
|
||||
}}
|
||||
>
|
||||
<Text className="text-base text-[#333]">{option.label}</Text>
|
||||
{props.interfaceLanguage === option.value ? (
|
||||
<Ionicons name="checkmark" size={20} color="#007AFF" />
|
||||
) : null}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</Modal>
|
||||
</View>
|
||||
|
||||
<View className="rounded-2xl border border-[#E5E5EA] bg-white p-5">
|
||||
<View className="flex-row items-start justify-between">
|
||||
@@ -425,19 +636,44 @@ export function AdvancedTab(props: AdvancedTabProps) {
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
<ConfigureOnWebCard
|
||||
title="Allow rescheduling past events"
|
||||
description="Enabling this option allows for past events to be rescheduled."
|
||||
eventTypeId={props.eventTypeId}
|
||||
browserTitle="Allow Rescheduling Past Events"
|
||||
/>
|
||||
<View className="rounded-2xl border border-[#E5E5EA] bg-white p-5">
|
||||
<View className="flex-row items-start justify-between">
|
||||
<View className="mr-3 flex-1">
|
||||
<Text className="mb-1 text-base font-medium text-[#333]">
|
||||
Allow rescheduling past events
|
||||
</Text>
|
||||
<Text className="text-sm text-[#666]">
|
||||
Enabling this option allows for past events to be rescheduled.
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={props.allowReschedulingPastEvents}
|
||||
onValueChange={props.setAllowReschedulingPastEvents}
|
||||
trackColor={{ false: "#E5E5EA", true: "#34C759" }}
|
||||
thumbColor="#FFFFFF"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ConfigureOnWebCard
|
||||
title="Allow booking through reschedule link"
|
||||
description="When enabled, users will be able to create a new booking when trying to reschedule a cancelled booking."
|
||||
eventTypeId={props.eventTypeId}
|
||||
browserTitle="Allow Booking Through Reschedule Link"
|
||||
/>
|
||||
<View className="rounded-2xl border border-[#E5E5EA] bg-white p-5">
|
||||
<View className="flex-row items-start justify-between">
|
||||
<View className="mr-3 flex-1">
|
||||
<Text className="mb-1 text-base font-medium text-[#333]">
|
||||
Allow booking through reschedule link
|
||||
</Text>
|
||||
<Text className="text-sm text-[#666]">
|
||||
When enabled, users will be able to create a new booking when trying to reschedule a
|
||||
cancelled booking.
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={props.allowBookingThroughRescheduleLink}
|
||||
onValueChange={props.setAllowBookingThroughRescheduleLink}
|
||||
trackColor={{ false: "#E5E5EA", true: "#34C759" }}
|
||||
thumbColor="#FFFFFF"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ConfigureOnWebCard
|
||||
title="Custom 'Reply-To' email"
|
||||
@@ -502,12 +738,22 @@ export function AdvancedTab(props: AdvancedTabProps) {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<ConfigureOnWebCard
|
||||
title="Optimized slots"
|
||||
description="Arrange time slots to optimize availability."
|
||||
eventTypeId={props.eventTypeId}
|
||||
browserTitle="Optimized Slots"
|
||||
/>
|
||||
<View className="rounded-2xl border border-[#E5E5EA] bg-white p-5">
|
||||
<View className="flex-row items-start justify-between">
|
||||
<View className="mr-3 flex-1">
|
||||
<Text className="mb-1 text-base font-medium text-[#333]">Optimized slots</Text>
|
||||
<Text className="text-sm text-[#666]">
|
||||
Arrange time slots to optimize availability.
|
||||
</Text>
|
||||
</View>
|
||||
<Switch
|
||||
value={props.showOptimizedSlots}
|
||||
onValueChange={props.setShowOptimizedSlots}
|
||||
trackColor={{ false: "#E5E5EA", true: "#34C759" }}
|
||||
thumbColor="#FFFFFF"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -124,7 +124,9 @@ interface EventTypeFormState {
|
||||
disableCancelling: boolean;
|
||||
disableRescheduling: boolean;
|
||||
sendCalVideoTranscription: boolean;
|
||||
autoTranslate: boolean;
|
||||
|
||||
interfaceLanguage: string;
|
||||
showOptimizedSlots: boolean;
|
||||
|
||||
// Seats
|
||||
seatsEnabled: boolean;
|
||||
@@ -728,32 +730,43 @@ export function buildPartialUpdatePayload(
|
||||
const metadataChanges: Record<string, unknown> = {};
|
||||
const originalMetadata = original.metadata || {};
|
||||
|
||||
if (
|
||||
currentState.disableCancelling !==
|
||||
(originalMetadata.disableCancelling || original.disableCancelling || false)
|
||||
) {
|
||||
metadataChanges.disableCancelling = currentState.disableCancelling;
|
||||
// Disable Cancelling
|
||||
const originalDisableCancelling =
|
||||
typeof original.disableCancelling === "object"
|
||||
? original.disableCancelling.disabled
|
||||
: original.disableCancelling || false;
|
||||
|
||||
if (currentState.disableCancelling !== originalDisableCancelling) {
|
||||
payload.disableCancelling = { disabled: currentState.disableCancelling };
|
||||
}
|
||||
|
||||
if (
|
||||
currentState.disableRescheduling !==
|
||||
(originalMetadata.disableRescheduling || original.disableRescheduling || false)
|
||||
) {
|
||||
metadataChanges.disableRescheduling = currentState.disableRescheduling;
|
||||
// Disable Rescheduling
|
||||
const originalDisableRescheduling =
|
||||
typeof original.disableRescheduling === "object"
|
||||
? original.disableRescheduling.disabled
|
||||
: original.disableRescheduling || false;
|
||||
|
||||
if (currentState.disableRescheduling !== originalDisableRescheduling) {
|
||||
payload.disableRescheduling = { disabled: currentState.disableRescheduling };
|
||||
}
|
||||
|
||||
if (
|
||||
currentState.sendCalVideoTranscription !==
|
||||
(originalMetadata.sendCalVideoTranscription || original.sendCalVideoTranscription || false)
|
||||
) {
|
||||
metadataChanges.sendCalVideoTranscription = currentState.sendCalVideoTranscription;
|
||||
// Cal Video Settings
|
||||
const originalSendTranscription = original.calVideoSettings?.sendTranscriptionEmails ?? false;
|
||||
|
||||
if (currentState.sendCalVideoTranscription !== originalSendTranscription) {
|
||||
payload.calVideoSettings = {
|
||||
sendTranscriptionEmails: currentState.sendCalVideoTranscription,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
currentState.autoTranslate !==
|
||||
(originalMetadata.autoTranslate || original.autoTranslate || false)
|
||||
) {
|
||||
metadataChanges.autoTranslate = currentState.autoTranslate;
|
||||
// Interface Language (API V2)
|
||||
if (currentState.interfaceLanguage !== (original.interfaceLanguage || "")) {
|
||||
payload.interfaceLanguage = currentState.interfaceLanguage || null;
|
||||
}
|
||||
|
||||
// Show Optimized Slots (API V2)
|
||||
if (currentState.showOptimizedSlots !== (original.showOptimizedSlots || false)) {
|
||||
payload.showOptimizedSlots = currentState.showOptimizedSlots;
|
||||
}
|
||||
|
||||
if ((currentState.calendarEventName || "") !== (originalMetadata.calendarEventName || "")) {
|
||||
|
||||
@@ -39,38 +39,6 @@ const formatTime12Hour = (time24: string): string => {
|
||||
return `${hour12Padded}:${min} ${period}`;
|
||||
};
|
||||
|
||||
// Format availability for display - groups days with same time range
|
||||
const formatAvailabilityDisplay = (
|
||||
availability: Record<number, ScheduleAvailability[]>
|
||||
): string[] => {
|
||||
const timeRangeMap: Record<string, number[]> = {};
|
||||
|
||||
Object.keys(availability).forEach((dayIndexStr) => {
|
||||
const dayIndex = Number(dayIndexStr);
|
||||
const slots = availability[dayIndex];
|
||||
if (slots && slots.length > 0) {
|
||||
slots.forEach((slot) => {
|
||||
const timeKey = `${slot.startTime}-${slot.endTime}`;
|
||||
if (!timeRangeMap[timeKey]) {
|
||||
timeRangeMap[timeKey] = [];
|
||||
}
|
||||
timeRangeMap[timeKey].push(dayIndex);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const formatted: string[] = [];
|
||||
Object.keys(timeRangeMap).forEach((timeKey) => {
|
||||
const days = timeRangeMap[timeKey].sort((a, b) => a - b);
|
||||
const [startTime, endTime] = timeKey.split("-");
|
||||
const dayNames = days.map((day) => DAYS[day]).join(", ");
|
||||
const timeRange = `${formatTime12Hour(startTime)} - ${formatTime12Hour(endTime)}`;
|
||||
formatted.push(`${dayNames}, ${timeRange}`);
|
||||
});
|
||||
|
||||
return formatted;
|
||||
};
|
||||
|
||||
// Format date for display
|
||||
const formatDateForDisplay = (dateStr: string): string => {
|
||||
if (!dateStr) return "";
|
||||
|
||||
@@ -39,38 +39,6 @@ const formatTime12Hour = (time24: string): string => {
|
||||
return `${hour12Padded}:${min} ${period}`;
|
||||
};
|
||||
|
||||
// Format availability for display - groups days with same time range
|
||||
const formatAvailabilityDisplay = (
|
||||
availability: Record<number, ScheduleAvailability[]>
|
||||
): string[] => {
|
||||
const timeRangeMap: Record<string, number[]> = {};
|
||||
|
||||
Object.keys(availability).forEach((dayIndexStr) => {
|
||||
const dayIndex = Number(dayIndexStr);
|
||||
const slots = availability[dayIndex];
|
||||
if (slots && slots.length > 0) {
|
||||
slots.forEach((slot) => {
|
||||
const timeKey = `${slot.startTime}-${slot.endTime}`;
|
||||
if (!timeRangeMap[timeKey]) {
|
||||
timeRangeMap[timeKey] = [];
|
||||
}
|
||||
timeRangeMap[timeKey].push(dayIndex);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const formatted: string[] = [];
|
||||
Object.keys(timeRangeMap).forEach((timeKey) => {
|
||||
const days = timeRangeMap[timeKey].sort((a, b) => a - b);
|
||||
const [startTime, endTime] = timeKey.split("-");
|
||||
const dayNames = days.map((day) => DAYS[day]).join(", ");
|
||||
const timeRange = `${formatTime12Hour(startTime)} - ${formatTime12Hour(endTime)}`;
|
||||
formatted.push(`${dayNames}, ${timeRange}`);
|
||||
});
|
||||
|
||||
return formatted;
|
||||
};
|
||||
|
||||
// Format date for display
|
||||
const formatDateForDisplay = (dateStr: string): string => {
|
||||
if (!dateStr) return "";
|
||||
|
||||
@@ -1240,6 +1240,7 @@ function sanitizePayload(payload: Record<string, unknown>): Record<string, unkno
|
||||
"slotInterval",
|
||||
"eventName",
|
||||
"timeZone",
|
||||
"interfaceLanguage",
|
||||
];
|
||||
|
||||
for (const [key, value] of Object.entries(payload)) {
|
||||
|
||||
@@ -77,6 +77,29 @@ export interface EmailSettings {
|
||||
additionalGuestsEmails?: string[];
|
||||
}
|
||||
|
||||
// Disable Cancelling settings (API V2 format)
|
||||
export interface DisableCancelling {
|
||||
disabled: boolean;
|
||||
}
|
||||
|
||||
// Disable Rescheduling settings (API V2 format)
|
||||
export interface DisableRescheduling {
|
||||
disabled: boolean;
|
||||
minutesBefore?: number; // Optional: disable when less than X minutes before
|
||||
}
|
||||
|
||||
// Cal Video Settings
|
||||
export interface CalVideoSettings {
|
||||
disableRecordingForOrganizer?: boolean;
|
||||
disableRecordingForGuests?: boolean;
|
||||
redirectUrlOnExit?: string;
|
||||
enableAutomaticRecordingForOrganizer?: boolean;
|
||||
enableAutomaticTranscription?: boolean;
|
||||
disableTranscriptionForGuests?: boolean;
|
||||
disableTranscriptionForOrganizer?: boolean;
|
||||
sendTranscriptionEmails?: boolean;
|
||||
}
|
||||
|
||||
// Booking Field Types
|
||||
export interface BaseBookingField {
|
||||
name: string;
|
||||
@@ -231,23 +254,30 @@ export interface EventType {
|
||||
// Metadata
|
||||
metadata?: Record<string, unknown>;
|
||||
|
||||
// Booking action settings (may also be in metadata)
|
||||
disableRescheduling?: boolean;
|
||||
disableCancelling?: boolean;
|
||||
// Booking action settings (API V2 format)
|
||||
disableRescheduling?: DisableRescheduling;
|
||||
disableCancelling?: DisableCancelling;
|
||||
minimumRescheduleNotice?: number;
|
||||
allowReschedulingPastBookings?: boolean;
|
||||
allowReschedulingCancelledBookings?: boolean;
|
||||
|
||||
// Additional properties from API responses
|
||||
hideCalendarEventDetails?: boolean;
|
||||
hideOrganizerEmail?: boolean;
|
||||
allowReschedulingCancelledBookings?: boolean;
|
||||
customReplyToEmail?: string;
|
||||
color?: {
|
||||
lightThemeHex?: string;
|
||||
darkThemeHex?: string;
|
||||
};
|
||||
sendCalVideoTranscription?: boolean;
|
||||
autoTranslate?: boolean;
|
||||
|
||||
// Cal Video Settings
|
||||
calVideoSettings?: CalVideoSettings;
|
||||
|
||||
// Interface language (API V2)
|
||||
interfaceLanguage?: string;
|
||||
|
||||
// Optimized slots (API V2)
|
||||
showOptimizedSlots?: boolean;
|
||||
}
|
||||
|
||||
export interface CreateEventTypeInput {
|
||||
@@ -309,6 +339,13 @@ export interface CreateEventTypeInput {
|
||||
successRedirectUrl?: string;
|
||||
forwardParamsSuccessRedirect?: boolean;
|
||||
metadata?: Record<string, unknown>;
|
||||
|
||||
// API V2 new fields
|
||||
disableCancelling?: DisableCancelling;
|
||||
disableRescheduling?: DisableRescheduling;
|
||||
calVideoSettings?: CalVideoSettings;
|
||||
interfaceLanguage?: string;
|
||||
showOptimizedSlots?: boolean;
|
||||
}
|
||||
|
||||
export interface GetEventTypesResponse {
|
||||
|
||||
Reference in new issue
Block a user