fix(companion): iOS event type detail fixes (#26845)
* fix(companion): iOS event type detail fixes - Fix #1: Weekly schedule now shows multiple time slots per day in AvailabilityTab - Fix #2: Allow numeric input fields to be empty with proper validation - Fix #3: Add native iOS date picker for date range in Limits tab - Fix #4: Change 'Email verification' to 'Booker email verification' in Advanced tab - Fix #5: Fix Requires confirmation comparison logic in buildPartialUpdatePayload - Fix #6: Forward parameters toggle already working, verified wiring - Fix #7: Add toggles for redirect booking URL and interface language in Advanced tab - Fix #8: Increase scroll padding at bottom from 200 to 350 for better keyboard access * fix(companion): conditional scroll padding for limits and advanced tabs only * fix(companion): remove static date text from iOS date picker, keep only picker * fix(companion): simplify iOS date picker to match RescheduleScreen pattern * update code
This commit is contained in:
@@ -220,6 +220,7 @@ export default function EventTypeDetail() {
|
||||
const [requiresBookerEmailVerification, setRequiresBookerEmailVerification] = useState(false);
|
||||
const [hideCalendarNotes, setHideCalendarNotes] = useState(false);
|
||||
const [hideCalendarEventDetails, setHideCalendarEventDetails] = useState(false);
|
||||
const [redirectEnabled, setRedirectEnabled] = useState(false);
|
||||
const [successRedirectUrl, setSuccessRedirectUrl] = useState("");
|
||||
const [forwardParamsSuccessRedirect, setForwardParamsSuccessRedirect] = useState(false);
|
||||
const [hideOrganizerEmail, setHideOrganizerEmail] = useState(false);
|
||||
@@ -231,6 +232,7 @@ export default function EventTypeDetail() {
|
||||
const [customReplyToEmail, setCustomReplyToEmail] = useState("");
|
||||
const [eventTypeColorLight, setEventTypeColorLight] = useState("#292929");
|
||||
const [eventTypeColorDark, setEventTypeColorDark] = useState("#FAFAFA");
|
||||
const [interfaceLanguageEnabled, setInterfaceLanguageEnabled] = useState(false);
|
||||
const [interfaceLanguage, setInterfaceLanguage] = useState("");
|
||||
const [showOptimizedSlots, setShowOptimizedSlots] = useState(false);
|
||||
|
||||
@@ -673,8 +675,8 @@ export default function EventTypeDetail() {
|
||||
setSendCalVideoTranscription(false);
|
||||
}
|
||||
|
||||
// Load interface language (API V2)
|
||||
if (eventTypeExt.interfaceLanguage !== undefined) {
|
||||
if (eventTypeExt.interfaceLanguage) {
|
||||
setInterfaceLanguageEnabled(true);
|
||||
setInterfaceLanguage(eventTypeExt.interfaceLanguage);
|
||||
}
|
||||
|
||||
@@ -737,8 +739,8 @@ export default function EventTypeDetail() {
|
||||
setHideOrganizerEmail(eventTypeExt.hideOrganizerEmail);
|
||||
}
|
||||
|
||||
// Load redirect URL
|
||||
if (eventType.successRedirectUrl) {
|
||||
setRedirectEnabled(true);
|
||||
setSuccessRedirectUrl(eventType.successRedirectUrl);
|
||||
}
|
||||
if (eventType.forwardParamsSuccessRedirect !== undefined) {
|
||||
@@ -921,25 +923,34 @@ export default function EventTypeDetail() {
|
||||
const dayShort = day.substring(0, 3).toLowerCase(); // mon, tue, etc.
|
||||
const dayShortUpper = day.substring(0, 3).toUpperCase();
|
||||
|
||||
const availability = selectedScheduleDetails.availability?.find((avail) => {
|
||||
if (!avail.days || !Array.isArray(avail.days)) return false;
|
||||
// Find ALL matching availability slots for this day (not just the first one)
|
||||
const matchingSlots =
|
||||
selectedScheduleDetails.availability?.filter((avail) => {
|
||||
if (!avail.days || !Array.isArray(avail.days)) return false;
|
||||
|
||||
return avail.days.some(
|
||||
(d) =>
|
||||
d === dayLower ||
|
||||
d === dayUpper ||
|
||||
d === day ||
|
||||
d === dayShort ||
|
||||
d === dayShortUpper ||
|
||||
d.toLowerCase() === dayLower
|
||||
);
|
||||
});
|
||||
return avail.days.some(
|
||||
(d) =>
|
||||
d === dayLower ||
|
||||
d === dayUpper ||
|
||||
d === day ||
|
||||
d === dayShort ||
|
||||
d === dayShortUpper ||
|
||||
d.toLowerCase() === dayLower
|
||||
);
|
||||
}) || [];
|
||||
|
||||
// Map to time slots array
|
||||
const timeSlots = matchingSlots.map((slot) => ({
|
||||
startTime: slot.startTime,
|
||||
endTime: slot.endTime,
|
||||
}));
|
||||
|
||||
return {
|
||||
day,
|
||||
available: !!availability,
|
||||
startTime: availability?.startTime,
|
||||
endTime: availability?.endTime,
|
||||
available: timeSlots.length > 0,
|
||||
startTime: timeSlots[0]?.startTime,
|
||||
endTime: timeSlots[0]?.endTime,
|
||||
timeSlots, // Include all time slots for this day
|
||||
};
|
||||
});
|
||||
|
||||
@@ -1400,7 +1411,10 @@ export default function EventTypeDetail() {
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
contentContainerStyle={{ padding: 16, paddingBottom: 200 }}
|
||||
contentContainerStyle={{
|
||||
padding: 16,
|
||||
paddingBottom: activeTab === "limits" || activeTab === "advanced" ? 280 : 200,
|
||||
}}
|
||||
contentInsetAdjustmentBehavior="automatic"
|
||||
>
|
||||
{activeTab === "basics" ? (
|
||||
@@ -2161,6 +2175,8 @@ export default function EventTypeDetail() {
|
||||
setAllowReschedulingPastEvents={setAllowReschedulingPastEvents}
|
||||
allowBookingThroughRescheduleLink={allowBookingThroughRescheduleLink}
|
||||
setAllowBookingThroughRescheduleLink={setAllowBookingThroughRescheduleLink}
|
||||
redirectEnabled={redirectEnabled}
|
||||
setRedirectEnabled={setRedirectEnabled}
|
||||
successRedirectUrl={successRedirectUrl}
|
||||
setSuccessRedirectUrl={setSuccessRedirectUrl}
|
||||
forwardParamsSuccessRedirect={forwardParamsSuccessRedirect}
|
||||
@@ -2189,6 +2205,8 @@ export default function EventTypeDetail() {
|
||||
setDisableRescheduling={setDisableRescheduling}
|
||||
sendCalVideoTranscription={sendCalVideoTranscription}
|
||||
setSendCalVideoTranscription={setSendCalVideoTranscription}
|
||||
interfaceLanguageEnabled={interfaceLanguageEnabled}
|
||||
setInterfaceLanguageEnabled={setInterfaceLanguageEnabled}
|
||||
interfaceLanguage={interfaceLanguage}
|
||||
setInterfaceLanguage={setInterfaceLanguage}
|
||||
showOptimizedSlots={showOptimizedSlots}
|
||||
|
||||
@@ -61,20 +61,22 @@ export function LoginScreen() {
|
||||
<Text className="text-[17px] font-semibold text-white">Continue with Cal.com</Text>
|
||||
</TouchableOpacity>
|
||||
|
||||
{/* Sign up link */}
|
||||
<TouchableOpacity
|
||||
onPress={handleSignUp}
|
||||
className="mt-3 items-center justify-center py-1"
|
||||
style={Platform.OS === "web" ? { cursor: "pointer" } : undefined}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View>
|
||||
<Text className="text-[15px] text-gray-500">
|
||||
Don't have an account? <Text className="font-semibold text-gray-900">Sign up</Text>
|
||||
</Text>
|
||||
<View className="h-px bg-gray-400" style={{ marginTop: 2 }} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
{/* Sign up link - hidden on iOS */}
|
||||
{Platform.OS !== "ios" && (
|
||||
<TouchableOpacity
|
||||
onPress={handleSignUp}
|
||||
className="mt-3 items-center justify-center py-1"
|
||||
style={Platform.OS === "web" ? { cursor: "pointer" } : undefined}
|
||||
activeOpacity={0.7}
|
||||
>
|
||||
<View>
|
||||
<Text className="text-[15px] text-gray-500">
|
||||
Don't have an account? <Text className="font-semibold text-gray-900">Sign up</Text>
|
||||
</Text>
|
||||
<View className="h-px bg-gray-400" style={{ marginTop: 2 }} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -246,6 +246,8 @@ interface AdvancedTabProps {
|
||||
setAllowReschedulingPastEvents: (value: boolean) => void;
|
||||
allowBookingThroughRescheduleLink: boolean;
|
||||
setAllowBookingThroughRescheduleLink: (value: boolean) => void;
|
||||
redirectEnabled: boolean;
|
||||
setRedirectEnabled: (value: boolean) => void;
|
||||
successRedirectUrl: string;
|
||||
setSuccessRedirectUrl: (value: string) => void;
|
||||
forwardParamsSuccessRedirect: boolean;
|
||||
@@ -271,6 +273,8 @@ interface AdvancedTabProps {
|
||||
setDisableRescheduling: (value: boolean) => void;
|
||||
sendCalVideoTranscription: boolean;
|
||||
setSendCalVideoTranscription: (value: boolean) => void;
|
||||
interfaceLanguageEnabled: boolean;
|
||||
setInterfaceLanguageEnabled: (value: boolean) => void;
|
||||
interfaceLanguage: string;
|
||||
setInterfaceLanguage: (value: string) => void;
|
||||
showOptimizedSlots: boolean;
|
||||
@@ -294,10 +298,19 @@ export function AdvancedTab(props: AdvancedTabProps) {
|
||||
title="Requires confirmation"
|
||||
description="The booking needs to be manually confirmed before it is pushed to your calendar and a confirmation is sent."
|
||||
value={props.requiresConfirmation}
|
||||
onValueChange={props.setRequiresConfirmation}
|
||||
onValueChange={(value) => {
|
||||
if (value && props.seatsEnabled) {
|
||||
Alert.alert(
|
||||
"Disable 'Offer seats' first",
|
||||
"You need to:\n1. Disable 'Offer seats' and Save\n2. Then enable 'Requires confirmation' and Save again"
|
||||
);
|
||||
return;
|
||||
}
|
||||
props.setRequiresConfirmation(value);
|
||||
}}
|
||||
/>
|
||||
<SettingRow
|
||||
title="Email verification"
|
||||
title="Booker email verification"
|
||||
description="To ensure booker's email verification before scheduling events."
|
||||
value={props.requiresBookerEmailVerification}
|
||||
onValueChange={props.setRequiresBookerEmailVerification}
|
||||
@@ -385,7 +398,16 @@ export function AdvancedTab(props: AdvancedTabProps) {
|
||||
title="Offer seats"
|
||||
description="Offer seats for booking. This automatically disables guest & opt-in bookings."
|
||||
value={props.seatsEnabled}
|
||||
onValueChange={props.setSeatsEnabled}
|
||||
onValueChange={(value) => {
|
||||
if (value && props.requiresConfirmation) {
|
||||
Alert.alert(
|
||||
"Disable 'Requires confirmation' first",
|
||||
"You need to:\n1. Disable 'Requires confirmation' and Save\n2. Then enable 'Offer seats' and Save again"
|
||||
);
|
||||
return;
|
||||
}
|
||||
props.setSeatsEnabled(value);
|
||||
}}
|
||||
learnMoreUrl="https://cal.com/help/event-types/offer-seats"
|
||||
isLast
|
||||
/>
|
||||
@@ -438,15 +460,24 @@ export function AdvancedTab(props: AdvancedTabProps) {
|
||||
|
||||
{/* Language */}
|
||||
<SettingsGroup header="Language">
|
||||
<NavigationRow
|
||||
<SettingRow
|
||||
isFirst
|
||||
isLast
|
||||
title="Interface Language"
|
||||
value={getLanguageLabel(props.interfaceLanguage)}
|
||||
onPress={() => setShowLanguagePicker(true)}
|
||||
options={interfaceLanguageOptions}
|
||||
onSelect={props.setInterfaceLanguage}
|
||||
title="Custom interface language"
|
||||
description="Override the default browser language for the booking page."
|
||||
value={props.interfaceLanguageEnabled}
|
||||
onValueChange={props.setInterfaceLanguageEnabled}
|
||||
isLast={!props.interfaceLanguageEnabled}
|
||||
/>
|
||||
{props.interfaceLanguageEnabled ? (
|
||||
<NavigationRow
|
||||
isLast
|
||||
title="Select Language"
|
||||
value={getLanguageLabel(props.interfaceLanguage)}
|
||||
onPress={() => setShowLanguagePicker(true)}
|
||||
options={interfaceLanguageOptions}
|
||||
onSelect={props.setInterfaceLanguage}
|
||||
/>
|
||||
) : null}
|
||||
</SettingsGroup>
|
||||
|
||||
{/* Language Picker Modal */}
|
||||
@@ -527,34 +558,42 @@ export function AdvancedTab(props: AdvancedTabProps) {
|
||||
|
||||
{/* Redirect */}
|
||||
<SettingsGroup header="Redirect">
|
||||
<View className="bg-white pl-4">
|
||||
<View className="border-b border-[#E5E5E5] pt-4 pb-3 pr-4">
|
||||
<Text className="mb-2 text-[13px] text-[#6D6D72]">
|
||||
Redirect URL after successful booking
|
||||
</Text>
|
||||
<TextInput
|
||||
className="rounded-lg bg-[#F2F2F7] px-3 py-2 text-[17px] text-black"
|
||||
value={props.successRedirectUrl}
|
||||
onChangeText={props.setSuccessRedirectUrl}
|
||||
placeholder="https://example.com/thank-you"
|
||||
placeholderTextColor="#8E8E93"
|
||||
keyboardType="url"
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
{props.successRedirectUrl ? (
|
||||
<Text className="mt-2 text-[13px] text-[#FF9500]">
|
||||
Adding a redirect will disable the success page.
|
||||
</Text>
|
||||
) : null}
|
||||
</View>
|
||||
</View>
|
||||
<SettingRow
|
||||
title="Forward parameters"
|
||||
description="Forward parameters such as ?email=...&name=... to the redirect URL."
|
||||
value={props.forwardParamsSuccessRedirect}
|
||||
onValueChange={props.setForwardParamsSuccessRedirect}
|
||||
isLast
|
||||
isFirst
|
||||
title="Redirect on booking"
|
||||
description="Redirect to a custom URL after a successful booking."
|
||||
value={props.redirectEnabled}
|
||||
onValueChange={props.setRedirectEnabled}
|
||||
isLast={!props.redirectEnabled}
|
||||
/>
|
||||
{props.redirectEnabled ? (
|
||||
<>
|
||||
<View className="bg-white pl-4">
|
||||
<View className="border-b border-[#E5E5E5] pt-4 pb-3 pr-4">
|
||||
<Text className="mb-2 text-[13px] text-[#6D6D72]">Redirect URL</Text>
|
||||
<TextInput
|
||||
className="rounded-lg bg-[#F2F2F7] px-3 py-2 text-[17px] text-black"
|
||||
value={props.successRedirectUrl}
|
||||
onChangeText={props.setSuccessRedirectUrl}
|
||||
placeholder="https://example.com/thank-you"
|
||||
placeholderTextColor="#8E8E93"
|
||||
keyboardType="url"
|
||||
autoCapitalize="none"
|
||||
/>
|
||||
<Text className="mt-2 text-[13px] text-[#FF9500]">
|
||||
Adding a redirect will disable the success page.
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<SettingRow
|
||||
title="Forward parameters"
|
||||
description="Forward parameters such as ?email=...&name=... to the redirect URL."
|
||||
value={props.forwardParamsSuccessRedirect}
|
||||
onValueChange={props.setForwardParamsSuccessRedirect}
|
||||
isLast
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</SettingsGroup>
|
||||
|
||||
{/* Configure on Web Section */}
|
||||
|
||||
@@ -10,11 +10,17 @@ import { Platform, Text, TouchableOpacity, View } from "react-native";
|
||||
import type { Schedule } from "@/services/calcom";
|
||||
import { AvailabilityTabIOSPicker } from "./AvailabilityTabIOSPicker";
|
||||
|
||||
interface TimeSlot {
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
}
|
||||
|
||||
interface DaySchedule {
|
||||
day: string;
|
||||
available: boolean;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
timeSlots?: TimeSlot[];
|
||||
}
|
||||
|
||||
interface AvailabilityTabProps {
|
||||
@@ -181,6 +187,7 @@ export function AvailabilityTab(props: AvailabilityTabProps) {
|
||||
const dayInfo = daySchedules.find((d) => d.day === day);
|
||||
const isEnabled = dayInfo?.available ?? false;
|
||||
const isLast = index === DAYS.length - 1;
|
||||
const timeSlots = dayInfo?.timeSlots || [];
|
||||
|
||||
return (
|
||||
<View
|
||||
@@ -203,16 +210,25 @@ export function AvailabilityTab(props: AvailabilityTabProps) {
|
||||
{day}
|
||||
</Text>
|
||||
|
||||
{/* Time range or Unavailable */}
|
||||
<Text
|
||||
className={`flex-1 text-right text-[15px] ${
|
||||
isEnabled ? "text-black" : "text-[#8E8E93]"
|
||||
}`}
|
||||
>
|
||||
{isEnabled && dayInfo?.startTime && dayInfo?.endTime
|
||||
? `${formatTime12Hour(dayInfo.startTime)} - ${formatTime12Hour(dayInfo.endTime)}`
|
||||
: "Unavailable"}
|
||||
</Text>
|
||||
{/* Time ranges or Unavailable - support multiple time slots */}
|
||||
{isEnabled && timeSlots.length > 0 ? (
|
||||
<View className="flex-1 items-end">
|
||||
{timeSlots.map((slot, slotIndex) => (
|
||||
<Text
|
||||
key={`${slotIndex}-${slot.startTime}`}
|
||||
className={`text-[15px] text-black ${slotIndex > 0 ? "mt-1" : ""}`}
|
||||
>
|
||||
{slot.startTime && slot.endTime
|
||||
? `${formatTime12Hour(slot.startTime)} - ${formatTime12Hour(slot.endTime)}`
|
||||
: ""}
|
||||
</Text>
|
||||
))}
|
||||
</View>
|
||||
) : (
|
||||
<Text className="flex-1 text-right text-[15px] text-[#8E8E93]">
|
||||
Unavailable
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
View,
|
||||
} from "react-native";
|
||||
|
||||
import { LimitsTabDatePicker } from "./LimitsTabDatePicker";
|
||||
import { LimitsTabIOSPicker } from "./LimitsTabIOSPicker";
|
||||
|
||||
interface FrequencyLimit {
|
||||
@@ -301,10 +302,7 @@ export function LimitsTab(props: LimitsTabProps) {
|
||||
value={props.minimumNoticeValue}
|
||||
onChangeText={(text) => {
|
||||
const numericValue = text.replace(/[^0-9]/g, "");
|
||||
const num = parseInt(numericValue, 10) || 0;
|
||||
if (num >= 0) {
|
||||
props.setMinimumNoticeValue(numericValue || "0");
|
||||
}
|
||||
props.setMinimumNoticeValue(numericValue);
|
||||
}}
|
||||
placeholder="1"
|
||||
placeholderTextColor="#8E8E93"
|
||||
@@ -389,10 +387,7 @@ export function LimitsTab(props: LimitsTabProps) {
|
||||
value={limit.value}
|
||||
onChangeText={(text) => {
|
||||
const numericValue = text.replace(/[^0-9]/g, "");
|
||||
const num = parseInt(numericValue, 10) || 0;
|
||||
if (num >= 0) {
|
||||
props.updateFrequencyLimit(limit.id, "value", numericValue || "0");
|
||||
}
|
||||
props.updateFrequencyLimit(limit.id, "value", numericValue);
|
||||
}}
|
||||
placeholder="1"
|
||||
placeholderTextColor="#8E8E93"
|
||||
@@ -451,10 +446,7 @@ export function LimitsTab(props: LimitsTabProps) {
|
||||
value={limit.value}
|
||||
onChangeText={(text) => {
|
||||
const numericValue = text.replace(/[^0-9]/g, "");
|
||||
const num = parseInt(numericValue, 10) || 0;
|
||||
if (num >= 0) {
|
||||
props.updateDurationLimit(limit.id, "value", numericValue || "0");
|
||||
}
|
||||
props.updateDurationLimit(limit.id, "value", numericValue);
|
||||
}}
|
||||
placeholder="60"
|
||||
placeholderTextColor="#8E8E93"
|
||||
@@ -512,10 +504,7 @@ export function LimitsTab(props: LimitsTabProps) {
|
||||
value={props.maxActiveBookingsValue}
|
||||
onChangeText={(text) => {
|
||||
const numericValue = text.replace(/[^0-9]/g, "");
|
||||
const num = parseInt(numericValue, 10) || 0;
|
||||
if (num >= 0) {
|
||||
props.setMaxActiveBookingsValue(numericValue || "1");
|
||||
}
|
||||
props.setMaxActiveBookingsValue(numericValue);
|
||||
}}
|
||||
placeholder="1"
|
||||
placeholderTextColor="#8E8E93"
|
||||
@@ -560,7 +549,7 @@ export function LimitsTab(props: LimitsTabProps) {
|
||||
value={props.rollingDays}
|
||||
onChangeText={(text) => {
|
||||
const numericValue = text.replace(/[^0-9]/g, "");
|
||||
props.setRollingDays(numericValue || "30");
|
||||
props.setRollingDays(numericValue);
|
||||
props.setFutureBookingType("rolling");
|
||||
}}
|
||||
placeholder="30"
|
||||
@@ -602,21 +591,23 @@ export function LimitsTab(props: LimitsTabProps) {
|
||||
<View className="flex-1">
|
||||
<Text className="mb-2 text-[17px] text-black">Within a date range</Text>
|
||||
{props.futureBookingType === "range" ? (
|
||||
<View className="gap-2">
|
||||
<TextInput
|
||||
className="rounded-lg bg-[#F2F2F7] px-3 py-2 text-[15px] text-black"
|
||||
value={props.rangeStartDate}
|
||||
onChangeText={props.setRangeStartDate}
|
||||
placeholder="Start date (YYYY-MM-DD)"
|
||||
placeholderTextColor="#8E8E93"
|
||||
/>
|
||||
<TextInput
|
||||
className="rounded-lg bg-[#F2F2F7] px-3 py-2 text-[15px] text-black"
|
||||
value={props.rangeEndDate}
|
||||
onChangeText={props.setRangeEndDate}
|
||||
placeholder="End date (YYYY-MM-DD)"
|
||||
placeholderTextColor="#8E8E93"
|
||||
/>
|
||||
<View className="gap-3">
|
||||
<View className="flex-row items-center">
|
||||
<Text className="w-20 text-[13px] text-[#6D6D72]">Start date</Text>
|
||||
<LimitsTabDatePicker
|
||||
value={props.rangeStartDate}
|
||||
onChange={props.setRangeStartDate}
|
||||
placeholder="Select start date"
|
||||
/>
|
||||
</View>
|
||||
<View className="flex-row items-center">
|
||||
<Text className="w-20 text-[13px] text-[#6D6D72]">End date</Text>
|
||||
<LimitsTabDatePicker
|
||||
value={props.rangeEndDate}
|
||||
onChange={props.setRangeEndDate}
|
||||
placeholder="Select end date"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { DatePicker, Host } from "@expo/ui/swift-ui";
|
||||
import { useCallback, useMemo } from "react";
|
||||
|
||||
interface LimitsTabDatePickerProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export function LimitsTabDatePicker({ value, onChange }: LimitsTabDatePickerProps) {
|
||||
const dateValue = useMemo(() => {
|
||||
if (!value) return new Date();
|
||||
const parsed = new Date(`${value}T00:00:00`);
|
||||
return Number.isNaN(parsed.getTime()) ? new Date() : parsed;
|
||||
}, [value]);
|
||||
|
||||
const handleDateChange = useCallback(
|
||||
(date: Date) => {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
onChange(`${year}-${month}-${day}`);
|
||||
},
|
||||
[onChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<Host matchContents>
|
||||
<DatePicker
|
||||
onDateChange={handleDateChange}
|
||||
displayedComponents={["date"]}
|
||||
selection={dateValue}
|
||||
/>
|
||||
</Host>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { TextInput, View } from "react-native";
|
||||
|
||||
interface LimitsTabDatePickerProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export function LimitsTabDatePicker({ value, onChange, placeholder }: LimitsTabDatePickerProps) {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
className="rounded-lg bg-[#F2F2F7] px-3 py-2 text-[15px] text-black"
|
||||
value={value}
|
||||
onChangeText={onChange}
|
||||
placeholder={placeholder || "YYYY-MM-DD"}
|
||||
placeholderTextColor="#8E8E93"
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
@@ -643,11 +643,18 @@ export function buildPartialUpdatePayload(
|
||||
}
|
||||
|
||||
const originalRequiresConfirmation =
|
||||
original.requiresConfirmation ||
|
||||
original.requiresConfirmation === true ||
|
||||
(original.confirmationPolicy &&
|
||||
!("disabled" in original.confirmationPolicy && original.confirmationPolicy.disabled));
|
||||
if (currentState.requiresConfirmation !== originalRequiresConfirmation) {
|
||||
payload.requiresConfirmation = currentState.requiresConfirmation;
|
||||
!(
|
||||
"disabled" in original.confirmationPolicy && original.confirmationPolicy.disabled === true
|
||||
));
|
||||
if (currentState.requiresConfirmation !== !!originalRequiresConfirmation) {
|
||||
// API V2 expects confirmationPolicy object, not requiresConfirmation boolean
|
||||
if (currentState.requiresConfirmation) {
|
||||
payload.confirmationPolicy = { type: "always" };
|
||||
} else {
|
||||
payload.confirmationPolicy = { disabled: true };
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
|
||||
Reference in New Issue
Block a user