Skip to content

fix(companion): event type links for org user#5

Open
tomerqodo wants to merge 7 commits intoclaude_claude_vs_qodo_base_fixcompanion_event_type_links_for_org_user_pr5from
claude_claude_vs_qodo_head_fixcompanion_event_type_links_for_org_user_pr5
Open

fix(companion): event type links for org user#5
tomerqodo wants to merge 7 commits intoclaude_claude_vs_qodo_base_fixcompanion_event_type_links_for_org_user_pr5from
claude_claude_vs_qodo_head_fixcompanion_event_type_links_for_org_user_pr5

Conversation

@tomerqodo
Copy link
Copy Markdown

Benchmark PR from agentic-review-benchmarks#5

dhairyashiil and others added 7 commits January 25, 2026 12:01
Addresses Cubic AI review feedback (confidence 9/10): The username was
hardcoded to 'username' but still used by BasicsTab as a fallback for
URL display when bookingUrl is unavailable. This restores the useEffect
that fetches the actual username from CalComAPIService.getUsername().

Co-Authored-By: unknown <>
Comment on lines +1125 to +1128
const bookingUrl =
`https://cal.com/${
eventType.users?.[0]?.username || "user"
}/${eventType.slug}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 The first copy button handler (line 1125) was only reformatted but not updated to use eventType.bookingUrl || fallback, unlike the preview handler directly above it (line 1081) and the second copy handler (line 1871), which were both correctly updated. For org users, clicking "Copy Link" on event types in the first section will always produce https://cal.com/... instead of the org-specific booking URL, while Preview works correctly right next to it.

Extended reasoning...

What the bug is

This PR adds bookingUrl support for org users across the companion extension. The content.ts file has two sets of event type UI (for different contexts), each with a preview button handler and a copy button handler. All four handlers previously hardcoded https://cal.com/username/slug. The PR correctly updated three of these four handlers to use eventType.bookingUrl || fallback, but missed the first copy handler.

The specific code path

In content.ts, the first set of event type buttons has:

  • Preview handler (line 1081-1085): Correctly updated to eventType.bookingUrl || https://cal.com/...
  • Copy handler (line 1125-1128): Only reformatted (whitespace changes) - still uses https://cal.com/... without the eventType.bookingUrl || prefix

Meanwhile, the second set of buttons has both handlers correctly updated:

  • Preview handler (line 1826-1828): Uses eventType.bookingUrl || fallback
  • Copy handler (line 1871-1873): Uses eventType.bookingUrl || fallback

Why existing code does not prevent it

There is no shared utility function for building booking URLs in content.ts. Each handler constructs the URL inline, so each occurrence must be updated independently. This one was simply missed during the PR.

Step-by-step proof

  1. An org user (e.g., with booking URL https://i.cal.com/keith/30min) loads a page where the extension injects event type buttons (first context).
  2. The user clicks Preview - the preview handler reads eventType.bookingUrl which is https://i.cal.com/keith/30min and opens the correct URL.
  3. The user clicks Copy Link - the copy handler ignores eventType.bookingUrl entirely and constructs https://cal.com/keith/30min, copying the WRONG URL.
  4. The user pastes the copied link and sends it to someone. The recipient gets a broken or incorrect link.

Impact

Org users who copy event type links from the first section of the browser extension will share incorrect URLs. This defeats the core purpose of the PR for this specific action, creating an inconsistency where Preview works but Copy Link does not.

How to fix

Change line 1125-1128 from:

const bookingUrl =
  `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`;

to:

const bookingUrl =
  eventType.bookingUrl ||
  `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`;

This matches the pattern used in the preview handler above and the second copy handler.

Comment on lines +949 to 951
if (!bookingUrl) {
showErrorAlert("Error", "Booking URL not available. Please save the event type first.");
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 setBookingUrl is never called anywhere in this component, so the bookingUrl state remains "" forever. This means handlePreview and handleCopyLink always show "Booking URL not available" errors, and BasicsTab always falls back to cal.com/{username}/ instead of showing the correct org URL. Add if (eventType.bookingUrl) setBookingUrl(eventType.bookingUrl); inside applyEventTypeData.

Extended reasoning...

What the bug is

The PR introduces a new bookingUrl state variable at line 180 (const [bookingUrl, setBookingUrl] = useState<string>("")) and replaces the old buildEventTypeLink() calls in handlePreview and handleCopyLink with this state variable. However, setBookingUrl is never called anywhere in the entire component — the setter function is completely unused after declaration.

The specific code path

When event type data is fetched from the API, the applyEventTypeData callback (starting around line 451) is invoked. This callback meticulously loads dozens of fields from the fetched event type: title, slug, description, lengthInMinutes, hidden, scheduleId, locations, beforeEventBuffer, afterEventBuffer, minimumBookingNotice, slotInterval, bookingLimitsCount, disableCancelling, disableRescheduling, recurrence, seats, and many more. But it conspicuously omits any call to setBookingUrl(eventType.bookingUrl). Since bookingUrl is initialized to "" (empty string, which is falsy), it never changes.

Step-by-step proof

  1. User navigates to event-type-detail for an existing event type
  2. fetchEventTypeData() is called, which fetches the event type from the API (the API now returns a bookingUrl field per the type changes in this PR)
  3. applyEventTypeData(eventType) is called with the fetched data
  4. applyEventTypeData sets title, slug, description, duration, hidden, scheduleId, etc. — but never calls setBookingUrl(eventType.bookingUrl)
  5. bookingUrl state remains ""
  6. User taps "Preview" → handlePreview at line 949 checks if (!bookingUrl) → true → shows error "Booking URL not available. Please save the event type first."
  7. User taps "Copy Link" → handleCopyLink at line 957 → same guard → same error
  8. BasicsTab receives bookingUrl="" as a prop → the URL prefix IIFE at BasicsTab line 254 checks if (props.bookingUrl) → false → falls back to cal.com/{props.username}/

Impact

This completely breaks three features in the event type detail view:

  • Preview always fails with an error message
  • Copy Link always fails with an error message
  • URL prefix in BasicsTab always shows the wrong domain for org users (the entire purpose of this PR)

Notably, the list views (index.tsx, index.ios.tsx) work correctly because they use eventType.bookingUrl directly from the fetched data object, not from component state. The bug is isolated to the detail view where local state was introduced but never populated.

Fix

Add if (eventType.bookingUrl) setBookingUrl(eventType.bookingUrl); inside the applyEventTypeData callback, alongside the other field assignments. This is a one-line fix that follows the exact same pattern as every other field loaded in that function.

Note on duplicate concern

One verifier flagged this as a potential duplicate of bug_001. If bug_001 covers the same root cause (missing setBookingUrl call in applyEventTypeData), these are indeed the same underlying issue. However, this report provides the complete analysis of all three affected code paths (preview, copy link, and BasicsTab URL prefix) for full visibility.

Comment on lines 113 to 120
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!");
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 The mobile app handleCopyLink, handlePreview, and _handleShare functions in both index.ios.tsx and index.tsx show an error and return early when eventType.bookingUrl is absent, with no fallback URL construction. Since bookingUrl is typed as optional (bookingUrl?: string), this will break copy/preview/share for any event type where the API does not return it. The extension code (content.ts, linkedin.ts) correctly handles this with a fallback to constructing the URL from username + slug -- the mobile app should use the same pattern.

Extended reasoning...

What the bug is

The PR removes CalComAPIService.buildEventTypeLink() from the mobile app and replaces it with direct usage of eventType.bookingUrl. However, the replacement code has no fallback when bookingUrl is undefined -- it simply shows an error alert ("Booking URL not available for this event type.") and returns early. This affects six functions across two files:

  • index.ios.tsx: handleCopyLink (line 113), _handleShare (line 127), handlePreview (line 234)
  • index.tsx: handleCopyLink (line 135), _handleShare (line 146), handlePreview (line 287)

The inconsistency within this PR

The extension code in the same PR handles this correctly. In content.ts and linkedin.ts, the pattern used is:

const bookingUrl = eventType.bookingUrl || `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`;

This defensive fallback ensures the feature always works even when bookingUrl is absent. The mobile app code does not use any fallback.

Why existing code doesn't prevent it

The EventType interface in event-types.types.ts explicitly types bookingUrl as optional: bookingUrl?: string. This means the TypeScript type system allows bookingUrl to be undefined. The old code used CalComAPIService.buildEventTypeLink(eventType.slug) which always worked by constructing a URL from the user's username and slug -- this fallback has been removed entirely for the mobile app.

Step-by-step proof

  1. User opens the mobile app and views their event types list
  2. The API returns an event type where bookingUrl is not populated (e.g., a newly created event type, an older API response, or a non-org user)
  3. User taps the context menu and selects "Copy link", "Preview", or "Share"
  4. The handler checks if (!eventType.bookingUrl) -- this is true since bookingUrl is undefined
  5. showErrorAlert("Error", "Booking URL not available for this event type.") is called
  6. The function returns early -- the user cannot copy, preview, or share the link
  7. Meanwhile, the same event type works perfectly in the browser extension because it falls back to constructing the URL from username + slug

Impact

Copy link, preview, and share are core features for event types. If the API ever returns an event type without bookingUrl, all three actions become completely non-functional on mobile, showing an unhelpful error message. The old code always worked because it constructed the URL locally.

How to fix

Apply the same fallback pattern used in the extension code. For example, in the event type list handlers:

const url = eventType.bookingUrl || `https://cal.com/${eventType.users?.[0]?.username || "user"}/${eventType.slug}`;

This should be applied to all six affected functions across both index.ios.tsx and index.tsx.

duration?: number;
description?: string;
users?: Array<{ username?: string }>;
bookingUrl?: string;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟣 Pre-existing: The Gmail integration section of content.ts still hardcodes https://cal.com/ URLs in three places (lines 2233, 2297, 3685), producing incorrect booking links for org users (e.g., cal.com/keith/30min instead of i.cal.com/keith/30min). The PR fixes this exact pattern elsewhere in the file but missed these three Gmail locations. The Gmail section type definitions (lines 2899-2905, 3293-3300) and the generateEmailEmbedHTML function signature (line 2188) also lack bookingUrl.

Extended reasoning...

What the bug is

The PR systematically replaces hardcoded https://cal.com/ URL construction with eventType.bookingUrl || fallback throughout content.ts (and other files). However, three locations in the Gmail integration section of the same file were missed and still manually construct URLs using https://cal.com/username/slug.

Affected locations

  1. Line 2233 (generateEmailEmbedHTML): Constructs slot-specific booking URLs as https://cal.com/username/slug?duration=...
  2. Line 2297 (generateEmailEmbedHTML): The "See all available times" link hardcodes https://cal.com/username/slug?cal.tz=...
  3. Line 3685 (Gmail slot insertion handler): Constructs https://cal.com/selectedUsername/selectedSlug as the base URL for inserted calendar links

Additionally, the Gmail section type definitions at lines 2899-2905 and 3293-3300 do not include bookingUrl in their inline event type interfaces (unlike the updated sections at lines 836, 879, 894). The generateEmailEmbedHTML function signature at line 2188 defines its eventType parameter as { slug: string; title?: string } — also missing bookingUrl.

Step-by-step proof

  1. An org user (e.g., username "keith" on org domain i.cal.com) has event types fetched from the API. The API returns bookingUrl: "https://i.cal.com/keith/30min" for each event type.
  2. The PR updated the LinkedIn and messaging popup sections to use eventType.bookingUrl || fallback, so those generate correct URLs like https://i.cal.com/keith/30min.
  3. However, when the user uses the Gmail email embed feature, generateEmailEmbedHTML at line 2233 constructs https://cal.com/keith/30min?duration=30&date=... — wrong domain.
  4. The "See all available times" link at line 2297 similarly produces https://cal.com/keith/30min?cal.tz=... — wrong domain.
  5. When inserting a slot link via the Gmail slot insertion button at line 3685, it constructs https://cal.com/keith/30min — again wrong.
  6. All three URLs would 404 or show the wrong user's page since org users are hosted on their org subdomain, not cal.com.

Impact

Org users using the Gmail integration features (email embed with time slots, "See all available times" link, and direct slot insertion) will generate broken or incorrect booking URLs. Recipients clicking these links would not reach the correct booking page.

How to fix

  1. Add bookingUrl?: string to the Gmail section type definitions (lines 2899-2905 and 3293-3300).
  2. Add bookingUrl?: string to the generateEmailEmbedHTML eventType parameter type and pass it through.
  3. At lines 2233 and 2297, use eventType.bookingUrl to derive the base URL instead of hardcoding https://cal.com/username/...
  4. At line 3685, use selectedEventType.bookingUrl || fallback like the pattern used elsewhere in the PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants