Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export default function AvailabilityDetail() {
marginRight: -8,
}}
>
<Ionicons name="ellipsis-horizontal-circle" size={24} color="#007AFF" />
<Ionicons name="ellipsis-horizontal-circle" size={24} color="#000000" />
</TouchableOpacity>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
Expand Down
4 changes: 2 additions & 2 deletions companion/app/(tabs)/(bookings)/index.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export default function Bookings() {
label: currentFilterOption?.label || "Filter",
labelStyle: {
fontWeight: "600",
color: "#007AFF",
color: "#000000",
},
menu: {
title: "Filter by Status",
Expand Down Expand Up @@ -123,7 +123,7 @@ export default function Bookings() {
},
labelStyle: {
fontWeight: "600",
color: "#007AFF",
color: "#000000",
},
menu: {
title: menuItems.length > 0 ? "Filter by Event Type" : "No Event Types",
Expand Down
9 changes: 7 additions & 2 deletions companion/app/(tabs)/(bookings)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,16 @@ export default function Bookings() {
<DropdownMenuTrigger asChild>
<AppPressable
className="flex-row items-center rounded-lg border border-gray-200 bg-white"
style={{ paddingHorizontal: 8, paddingVertical: 6 }}
style={{
paddingHorizontal: 8,
paddingVertical: 6,
flexDirection: "row",
alignItems: "center",
}}
>
<Ionicons name="options-outline" size={14} color="#333" />
<Text
className={`text-sm ${selectedEventTypeId !== null ? "text-[#007AFF] font-semibold" : "text-[#333]"}`}
className={`text-sm ${selectedEventTypeId !== null ? "text-[#000000] font-semibold" : "text-[#333]"}`}
style={{ marginLeft: 4 }}
numberOfLines={1}
>
Expand Down
45 changes: 21 additions & 24 deletions companion/app/(tabs)/(event-types)/event-type-detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ export default function EventTypeDetail() {
const [conferencingOptions, setConferencingOptions] = useState<ConferencingOption[]>([]);
const [conferencingLoading, setConferencingLoading] = useState(false);
const [eventTypeData, setEventTypeData] = useState<EventType | null>(null);
const [bookingUrl, setBookingUrl] = useState<string>("");
const [saving, setSaving] = useState(false);
const [beforeEventBuffer, setBeforeEventBuffer] = useState("No buffer time");
const [afterEventBuffer, setAfterEventBuffer] = useState("No buffer time");
Expand Down Expand Up @@ -945,29 +946,19 @@ export default function EventTypeDetail() {
};

const handlePreview = async () => {
const eventTypeSlug = eventSlug || "preview";
let link: string;
try {
link = await CalComAPIService.buildEventTypeLink(eventTypeSlug);
} catch (error) {
safeLogError("Failed to generate preview link:", error);
showErrorAlert("Error", "Failed to generate preview link. Please try again.");
if (!bookingUrl) {
showErrorAlert("Error", "Booking URL not available. Please save the event type first.");
return;
}
await openInAppBrowser(link, "event type preview");
await openInAppBrowser(bookingUrl, "event type preview");
Comment on lines +949 to +953
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Detail link always missing 🐞 Bug ✓ Correctness

EventTypeDetail now gates Preview/Copy on a local bookingUrl state, but that state is
  initialized to "" and never set from fetched EventType data.
• This causes Preview and Copy Link to always show “Booking URL not available…” even when the event
  type has a valid slug/username.
• Users lose core functionality (preview/copy) in the event type detail screen.
Agent prompt
### Issue description
`EventTypeDetail` uses a local `bookingUrl` state for preview/copy, but it is never set from the fetched event type. This makes preview/copy always fail.

### Issue Context
`CalComAPIService.getEventTypeById(...)` returns an `EventType` object which can contain `bookingUrl` (optional). Even when `bookingUrl` is missing, the screen already has enough info (username + slug) to build a fallback URL.

### Fix Focus Areas
- companion/app/(tabs)/(event-types)/event-type-detail.tsx[174-183]
- companion/app/(tabs)/(event-types)/event-type-detail.tsx[451-470]
- companion/app/(tabs)/(event-types)/event-type-detail.tsx[948-963]

### Suggested approach
- Prefer removing the separate `bookingUrl` state entirely and compute it via `useMemo` from `eventTypeData?.bookingUrl` with a fallback `https://cal.com/${username}/${eventSlug}` when possible.
- If keeping state, set it in `applyEventTypeData` (e.g., `setBookingUrl(eventType.bookingUrl ?? "")`) and/or add an effect to recompute when `username`/`eventSlug` change.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

};

const handleCopyLink = async () => {
const eventTypeSlug = eventSlug || "event-link";
let link: string;
try {
link = await CalComAPIService.buildEventTypeLink(eventTypeSlug);
} catch (error) {
safeLogError("Failed to copy link:", error);
showErrorAlert("Error", "Failed to copy link. Please try again.");
if (!bookingUrl) {
showErrorAlert("Error", "Booking URL not available. Please save the event type first.");
return;
}
await Clipboard.setStringAsync(link);
await Clipboard.setStringAsync(bookingUrl);
showSuccessAlert("Success", "Link copied!");
};

Expand Down Expand Up @@ -1279,7 +1270,10 @@ export default function EventTypeDetail() {
{/* Tab Navigation Dropdown Menu */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<AppPressable className="flex-row items-center gap-1 px-2 py-2">
<AppPressable
className="flex-row items-center gap-1 px-2 py-2"
style={{ flexDirection: "row", alignItems: "center" }}
>
<Text className="text-[16px] font-semibold text-[#000000]" numberOfLines={1}>
{tabs.find((tab) => tab.id === activeTab)?.label ?? "Basics"}
</Text>
Expand Down Expand Up @@ -1433,6 +1427,7 @@ export default function EventTypeDetail() {
onUpdateLocation={handleUpdateLocation}
locationOptions={getLocationOptionsForDropdown()}
conferencingLoading={conferencingLoading}
bookingUrl={bookingUrl}
/>
) : null}

Expand Down Expand Up @@ -2342,15 +2337,17 @@ export default function EventTypeDetail() {
<View className="bg-white pl-4">
<View
className="flex-row items-center justify-between pr-4"
style={{ height: 44 }}
style={{ height: 44, flexDirection: "row", alignItems: "center" }}
>
<Text className="text-[17px] text-black">Hidden</Text>
<Switch
value={isHidden}
onValueChange={setIsHidden}
trackColor={{ false: "#E5E5EA", true: "#000000" }}
thumbColor="#FFFFFF"
/>
<View style={{ justifyContent: "center", height: "100%" }}>
<Switch
value={isHidden}
onValueChange={setIsHidden}
trackColor={{ false: "#E5E5EA", true: "#000000" }}
thumbColor="#FFFFFF"
/>
</View>
</View>
</View>
</View>
Expand Down
28 changes: 18 additions & 10 deletions companion/app/(tabs)/(event-types)/index.ios.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button, ContextMenu, Host, HStack, Image as SwiftUIImage } from "@expo/ui/swift-ui";
import { Button, Host, Image as SwiftUIImage } from "@expo/ui/swift-ui";
import * as Haptics from "expo-haptics";
import { buttonStyle, controlSize, fixedSize, frame, padding } from "@expo/ui/swift-ui/modifiers";
import { buttonStyle, controlSize, frame, padding } from "@expo/ui/swift-ui/modifiers";
import { Ionicons } from "@expo/vector-icons";
import * as Clipboard from "expo-clipboard";
import { isLiquidGlassAvailable } from "expo-glass-effect";
Expand Down Expand Up @@ -28,7 +28,7 @@ import {
useUserProfile,
} from "@/hooks";
import { useEventTypeFilter } from "@/hooks/useEventTypeFilter";
import { CalComAPIService, type EventType } from "@/services/calcom";
import type { EventType } from "@/services/calcom";
import { showErrorAlert, showSuccessAlert } from "@/utils/alerts";
import { openInAppBrowser } from "@/utils/browser";
import { getAvatarUrl } from "@/utils/getAvatarUrl";
Expand Down Expand Up @@ -111,21 +111,27 @@ export default function EventTypesIOS() {
};

const handleCopyLink = async (eventType: EventType) => {
if (!eventType.bookingUrl) {
showErrorAlert("Error", "Booking URL not available for this event type.");
return;
}
try {
const link = await CalComAPIService.buildEventTypeLink(eventType.slug);
await Clipboard.setStringAsync(link);
await Clipboard.setStringAsync(eventType.bookingUrl);
showSuccessAlert("Link Copied", "Event type link copied!");
} catch {
showErrorAlert("Error", "Failed to copy link. Please try again.");
}
};

const _handleShare = async (eventType: EventType) => {
if (!eventType.bookingUrl) {
showErrorAlert("Error", "Booking URL not available for this event type.");
return;
}
try {
const link = await CalComAPIService.buildEventTypeLink(eventType.slug);
await Share.share({
message: `Book a meeting: ${eventType.title}`,
url: link,
url: eventType.bookingUrl,
});
} catch {
showErrorAlert("Error", "Failed to share link. Please try again.");
Expand Down Expand Up @@ -226,10 +232,12 @@ export default function EventTypesIOS() {
};

const handlePreview = async (eventType: EventType) => {
if (!eventType.bookingUrl) {
showErrorAlert("Error", "Booking URL not available for this event type.");
return;
}
try {
const link = await CalComAPIService.buildEventTypeLink(eventType.slug);
// For mobile, use in-app browser
await openInAppBrowser(link, "event type preview");
await openInAppBrowser(eventType.bookingUrl, "event type preview");
} catch {
console.error("Failed to open preview");
showErrorAlert("Error", "Failed to open preview. Please try again.");
Expand Down
30 changes: 19 additions & 11 deletions companion/app/(tabs)/(event-types)/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
useEventTypes,
} from "@/hooks";
import { useEventTypeFilter } from "@/hooks/useEventTypeFilter";
import { CalComAPIService, type EventType } from "@/services/calcom";
import type { EventType } from "@/services/calcom";
import { showErrorAlert, showSuccessAlert } from "@/utils/alerts";
import { openInAppBrowser } from "@/utils/browser";
import { getEventDuration } from "@/utils/getEventDuration";
Expand Down Expand Up @@ -133,22 +133,27 @@ export default function EventTypes() {
};

const handleCopyLink = async (eventType: EventType) => {
if (!eventType.bookingUrl) {
showErrorAlert("Error", "Booking URL not available for this event type.");
return;
}
try {
const link = await CalComAPIService.buildEventTypeLink(eventType.slug);
await Clipboard.setStringAsync(link);

await Clipboard.setStringAsync(eventType.bookingUrl);
showSuccessAlert("Link Copied", "Event type link copied!");
Comment on lines +136 to 142
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. Link actions too strict 🐞 Bug ⛯ Reliability

• EventTypes list screens (iOS and cross-platform) now hard-require eventType.bookingUrl and show
  an error when it’s missing.
• bookingUrl is explicitly typed as optional, and other parts of the codebase handle its absence
  via fallback URL construction.
• If the API omits bookingUrl in some responses, share/copy/preview will fail even though the URL
  can be derived from username + slug.
Agent prompt
### Issue description
EventTypes screens now require `eventType.bookingUrl`, but the field is optional. If it’s missing in API responses, users can’t share/copy/preview even though the URL can be derived.

### Issue Context
Other parts of the code (e.g., extension LinkedIn integration) treat `bookingUrl` as optional and build a fallback from username + slug.

### Fix Focus Areas
- companion/app/(tabs)/(event-types)/index.tsx[135-161]
- companion/app/(tabs)/(event-types)/index.tsx[287-301]
- companion/app/(tabs)/(event-types)/index.ios.tsx[113-139]

### Suggested approach
Option A (UI-level fallback):
- Replace the early `if (!eventType.bookingUrl) return` with:
  - `const url = eventType.bookingUrl ?? derivedUrlFromUsernameAndSlug;`
  - Only error if neither is possible.

Option B (data-level normalization):
- In the service layer or hook that returns event types, ensure `bookingUrl` is always populated (compute fallback once) so the UI can treat it as required.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

} catch {
showErrorAlert("Error", "Failed to copy link. Please try again.");
}
};

const _handleShare = async (eventType: EventType) => {
if (!eventType.bookingUrl) {
showErrorAlert("Error", "Booking URL not available for this event type.");
return;
}
try {
const link = await CalComAPIService.buildEventTypeLink(eventType.slug);
await Share.share({
message: `Book a meeting: ${eventType.title}`,
url: link,
url: eventType.bookingUrl,
});
} catch {
showErrorAlert("Error", "Failed to share link. Please try again.");
Expand Down Expand Up @@ -280,14 +285,15 @@ export default function EventTypes() {
};

const handlePreview = async (eventType: EventType) => {
if (!eventType.bookingUrl) {
showErrorAlert("Error", "Booking URL not available for this event type.");
return;
}
try {
const link = await CalComAPIService.buildEventTypeLink(eventType.slug);
// Open in browser
if (Platform.OS === "web") {
window.open(link, "_blank");
window.open(eventType.bookingUrl, "_blank");
} else {
// For mobile, use in-app browser
await openInAppBrowser(link, "event type preview");
await openInAppBrowser(eventType.bookingUrl, "event type preview");
}
} catch {
console.error("Failed to open preview");
Expand Down Expand Up @@ -443,6 +449,7 @@ export default function EventTypes() {
/>
<TouchableOpacity
className="min-w-[60px] flex-row items-center justify-center gap-1 rounded-lg bg-black px-2.5 py-2"
style={{ flexDirection: "row", alignItems: "center", justifyContent: "center" }}
onPress={handleCreateNew}
>
<Ionicons name="add" size={18} color="#fff" />
Expand Down Expand Up @@ -507,6 +514,7 @@ export default function EventTypes() {
/>
<TouchableOpacity
className="min-w-[60px] flex-row items-center justify-center gap-1 rounded-lg bg-black px-2.5 py-2"
style={{ flexDirection: "row", alignItems: "center", justifyContent: "center" }}
onPress={handleCreateNew}
>
<Ionicons name="add" size={18} color="#fff" />
Expand Down
2 changes: 1 addition & 1 deletion companion/app/profile-sheet.ios.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export default function ProfileSheet() {
}}
>
{/* Profile Header */}
<View className="mt-20 border-b border-gray-200 px-6">
<View className="mt-20 px-6">
{isLoading ? (
<View className="items-center py-8">
<ActivityIndicator size="large" color="#000" />
Expand Down
2 changes: 1 addition & 1 deletion companion/app/profile-sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export default function ProfileSheet() {
contentContainerStyle={{ paddingBottom: insets.bottom + 20 }}
>
{/* Profile Header */}
<View className="border-b border-gray-200 px-6 py-6">
<View className="px-6 py-6">
{isLoading ? (
<View className="items-center py-8">
<ActivityIndicator size="large" color="#000" />
Expand Down
Loading