Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions companion/app/(tabs)/(availability)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { Stack } from "expo-router";
import { Platform } from "react-native";

export default function AvailabilityLayout() {
return (
<Stack>
<Stack.Screen name="index" options={{ headerShown: false }} />
<Stack.Screen name="availability-detail" options={{ headerShown: Platform.OS === "ios" }} />
</Stack>
);
}
118 changes: 118 additions & 0 deletions companion/app/(tabs)/(availability)/availability-detail.ios.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { useCallback, useRef } from "react";
import {
AvailabilityDetailScreen,
type AvailabilityDetailScreenHandle,
} from "@/components/screens/AvailabilityDetailScreen";

// Type for action handlers exposed by AvailabilityDetailScreen.ios.tsx
type ActionHandlers = {
handleSetAsDefault: () => void;
handleDelete: () => void;
};

export default function AvailabilityDetailIOS() {
const { id } = useLocalSearchParams<{ id: string }>();
const router = useRouter();

// Ref to store action handlers from AvailabilityDetailScreen
const actionHandlersRef = useRef<ActionHandlers | null>(null);
const screenRef = useRef<AvailabilityDetailScreenHandle>(null);

// Callback to receive action handlers from AvailabilityDetailScreen
const handleActionsReady = useCallback((handlers: ActionHandlers) => {
actionHandlersRef.current = handlers;
}, []);

// Navigation handlers for edit bottom sheets
const handleEditNameAndTimezone = useCallback(() => {
router.push(`/edit-availability-name?id=${id}` as never);
}, [router, id]);

const handleEditWorkingHours = useCallback(() => {
router.push(`/edit-availability-hours?id=${id}` as never);
}, [router, id]);

const handleEditOverride = useCallback(() => {
router.push(`/edit-availability-override?id=${id}` as never);
}, [router, id]);

// Action handlers for inline actions
const handleSetAsDefault = useCallback(() => {
if (actionHandlersRef.current?.handleSetAsDefault) {
actionHandlersRef.current.handleSetAsDefault();
} else if (screenRef.current?.setAsDefault) {
screenRef.current.setAsDefault();
}
}, []);

const handleDelete = useCallback(() => {
if (actionHandlersRef.current?.handleDelete) {
actionHandlersRef.current.handleDelete();
} else if (screenRef.current?.delete) {
screenRef.current.delete();
}
}, []);

if (!id) {
return null;
}

return (
<>
<Stack.Screen
options={{
title: "Availability",
headerBackTitle: "Availability",
headerBackButtonDisplayMode: "default",
headerTitle: "",
headerStyle: {
backgroundColor: "#f2f2f7",
},
headerShadowVisible: false,
}}
/>

<Stack.Header style={{ shadowColor: "transparent", backgroundColor: "#f2f2f7" }}>
<Stack.Header.Right>
{/* Edit Menu */}
<Stack.Header.Menu>
<Stack.Header.Label>Edit</Stack.Header.Label>

{/* Name and Timezone */}
<Stack.Header.MenuAction icon="pencil" onPress={handleEditNameAndTimezone}>
Name and Timezone
</Stack.Header.MenuAction>

{/* Working Hours */}
<Stack.Header.MenuAction icon="clock" onPress={handleEditWorkingHours}>
Working Hours
</Stack.Header.MenuAction>

{/* Date Override */}
<Stack.Header.MenuAction icon="calendar.badge.plus" onPress={handleEditOverride}>
Date Override
</Stack.Header.MenuAction>

{/* Set as Default */}
<Stack.Header.MenuAction icon="star" onPress={handleSetAsDefault}>
Set as Default
</Stack.Header.MenuAction>

{/* Delete */}
<Stack.Header.MenuAction icon="trash" onPress={handleDelete} destructive>
Delete Schedule
</Stack.Header.MenuAction>
</Stack.Header.Menu>
</Stack.Header.Right>
</Stack.Header>

<AvailabilityDetailScreen
ref={screenRef}
id={id}
// @ts-expect-error - onActionsReady is only available in AvailabilityDetailScreen.ios.tsx
onActionsReady={handleActionsReady}
/>
</>
);
}
2 changes: 1 addition & 1 deletion companion/app/(tabs)/(availability)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export default function Availability() {
onSuccess: (newSchedule) => {
// Navigate to edit the newly created schedule
router.push({
pathname: "/availability-detail",
pathname: "/(tabs)/(availability)/availability-detail",
params: {
id: newSchedule.id.toString(),
},
Expand Down
2 changes: 0 additions & 2 deletions companion/app/(tabs)/(event-types)/event-type-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
TouchableOpacity,
View,
} from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { AppPressable } from "@/components/AppPressable";
import { AdvancedTab } from "@/components/event-type-detail/tabs/AdvancedTab";
import { AvailabilityTab } from "@/components/event-type-detail/tabs/AvailabilityTab";
Expand Down Expand Up @@ -127,7 +126,6 @@ export default function EventTypeDetail() {
slug?: string;
}>();

const insets = useSafeAreaInsets();
const [activeTab, setActiveTab] = useState("basics");

// Form state
Expand Down
104 changes: 104 additions & 0 deletions companion/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,110 @@ function RootLayoutContent() {
headerBlurEffect: Platform.OS === "ios" && isLiquidGlassAvailable() ? undefined : "light",
}}
/>
<Stack.Screen
name="edit-availability-name"
options={{
headerShown: true,
headerTransparent: Platform.OS === "ios",
headerLargeTitle: false,
title: "Edit Name & Timezone",
presentation:
Platform.OS === "ios"
? isLiquidGlassAvailable()
? "formSheet"
: "modal"
: "containedModal",
sheetGrabberVisible: Platform.OS === "ios",
sheetAllowedDetents: Platform.OS === "ios" ? [0.5, 0.7] : undefined,
sheetInitialDetentIndex: Platform.OS === "ios" ? 0 : undefined,
contentStyle: {
backgroundColor:
Platform.OS === "ios" && isLiquidGlassAvailable() ? "transparent" : "#F2F2F7",
},
headerStyle: {
backgroundColor: Platform.OS === "ios" ? "transparent" : "#F2F2F7",
},
headerBlurEffect: Platform.OS === "ios" && isLiquidGlassAvailable() ? undefined : "light",
}}
/>
<Stack.Screen
name="edit-availability-hours"
options={{
headerShown: true,
headerTransparent: Platform.OS === "ios",
headerLargeTitle: false,
title: "Working Hours",
presentation:
Platform.OS === "ios"
? isLiquidGlassAvailable()
? "formSheet"
: "modal"
: "containedModal",
sheetGrabberVisible: Platform.OS === "ios",
sheetAllowedDetents: Platform.OS === "ios" ? [0.7, 1] : undefined,
sheetInitialDetentIndex: Platform.OS === "ios" ? 0 : undefined,
contentStyle: {
backgroundColor:
Platform.OS === "ios" && isLiquidGlassAvailable() ? "transparent" : "#F2F2F7",
},
headerStyle: {
backgroundColor: Platform.OS === "ios" ? "transparent" : "#F2F2F7",
},
headerBlurEffect: Platform.OS === "ios" && isLiquidGlassAvailable() ? undefined : "light",
}}
/>
<Stack.Screen
name="edit-availability-day"
options={{
headerShown: true,
headerTransparent: Platform.OS === "ios",
headerLargeTitle: false,
title: "Edit Day",
presentation:
Platform.OS === "ios"
? isLiquidGlassAvailable()
? "formSheet"
: "modal"
: "containedModal",
sheetGrabberVisible: Platform.OS === "ios",
sheetAllowedDetents: Platform.OS === "ios" ? [0.6, 0.9] : undefined,
sheetInitialDetentIndex: Platform.OS === "ios" ? 0 : undefined,
contentStyle: {
backgroundColor:
Platform.OS === "ios" && isLiquidGlassAvailable() ? "transparent" : "#F2F2F7",
},
headerStyle: {
backgroundColor: Platform.OS === "ios" ? "transparent" : "#F2F2F7",
},
headerBlurEffect: Platform.OS === "ios" && isLiquidGlassAvailable() ? undefined : "light",
}}
/>
<Stack.Screen
name="edit-availability-override"
options={{
headerShown: true,
headerTransparent: Platform.OS === "ios",
headerLargeTitle: false,
title: "Date Override",
presentation:
Platform.OS === "ios"
? isLiquidGlassAvailable()
? "formSheet"
: "modal"
: "containedModal",
sheetGrabberVisible: Platform.OS === "ios",
sheetAllowedDetents: Platform.OS === "ios" ? [0.7, 0.9] : undefined,
sheetInitialDetentIndex: Platform.OS === "ios" ? 0 : undefined,
contentStyle: {
backgroundColor:
Platform.OS === "ios" && isLiquidGlassAvailable() ? "transparent" : "#F2F2F7",
},
headerStyle: {
backgroundColor: Platform.OS === "ios" ? "transparent" : "#F2F2F7",
},
headerBlurEffect: Platform.OS === "ios" && isLiquidGlassAvailable() ? undefined : "light",
}}
/>
</Stack>
) : (
<LoginScreenComponent />
Expand Down
124 changes: 124 additions & 0 deletions companion/app/edit-availability-day.ios.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import { osName } from "expo-device";
import { isLiquidGlassAvailable } from "expo-glass-effect";
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import { useCallback, useEffect, useRef, useState } from "react";
import { ActivityIndicator, Alert, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import type { EditAvailabilityDayScreenHandle } from "@/components/screens/EditAvailabilityDayScreen.ios";
import EditAvailabilityDayScreenComponent from "@/components/screens/EditAvailabilityDayScreen.ios";
import { CalComAPIService, type Schedule } from "@/services/calcom";

const DAYS = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];

// Semi-transparent background to prevent black flash while preserving glass effect
const GLASS_BACKGROUND = "rgba(248, 248, 250, 0.01)";

function getPresentationStyle(): "formSheet" | "modal" {
if (isLiquidGlassAvailable() && osName !== "iPadOS") {
return "formSheet";
}
return "modal";
}

export default function EditAvailabilityDayIOS() {
const { id, day } = useLocalSearchParams<{ id: string; day: string }>();
const router = useRouter();
const insets = useSafeAreaInsets();
const [schedule, setSchedule] = useState<Schedule | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [isSaving, setIsSaving] = useState(false);

const screenRef = useRef<EditAvailabilityDayScreenHandle>(null);

const dayIndex = day ? parseInt(day, 10) : 0;
const dayName = DAYS[dayIndex] || "Day";

useEffect(() => {
if (id) {
setIsLoading(true);
CalComAPIService.getScheduleById(Number(id))
.then(setSchedule)
.catch(() => {
Alert.alert("Error", "Failed to load schedule details");
router.back();
})
.finally(() => setIsLoading(false));
} else {
setIsLoading(false);
Alert.alert("Error", "Schedule ID is missing");
router.back();
}
}, [id, router]);

const handleSave = useCallback(() => {
screenRef.current?.submit();
}, []);

const handleSuccess = useCallback(() => {
router.back();
}, [router]);

const presentationStyle = getPresentationStyle();
const useGlassEffect = isLiquidGlassAvailable();

return (
<>
<Stack.Screen
options={{
title: dayName,
presentation: presentationStyle,
sheetGrabberVisible: true,
sheetAllowedDetents: [0.6, 0.9],
sheetInitialDetentIndex: 0,
contentStyle: {
backgroundColor: useGlassEffect ? GLASS_BACKGROUND : "#F2F2F7",
},
}}
/>

<Stack.Header>
<Stack.Header.Left>
<Stack.Header.Button onPress={() => router.back()}>
<Stack.Header.Icon sf="xmark" />
</Stack.Header.Button>
</Stack.Header.Left>

<Stack.Header.Title>{dayName}</Stack.Header.Title>

<Stack.Header.Right>
<Stack.Header.Button
onPress={handleSave}
disabled={isSaving}
variant="prominent"
tintColor="#000"
>
<Stack.Header.Icon sf="checkmark" />
</Stack.Header.Button>
</Stack.Header.Right>
</Stack.Header>

<View
style={{
flex: 1,
paddingTop: 56,
paddingBottom: insets.bottom,
}}
>
{isLoading ? (
<View className="flex-1 items-center justify-center">
<ActivityIndicator size="large" color="#007AFF" />
</View>
) : (
<EditAvailabilityDayScreenComponent
ref={screenRef}
schedule={schedule}
dayIndex={dayIndex}
onSuccess={handleSuccess}
onSavingChange={setIsSaving}
transparentBackground={useGlassEffect}
/>
)}
</View>
</>
);
}
Loading
Loading