Skip to content

Comments

feat: allow emails and invite people to a team event-type#23314

Closed
anglerfishlyy wants to merge 2 commits intocalcom:mainfrom
anglerfishlyy:patch-1
Closed

feat: allow emails and invite people to a team event-type#23314
anglerfishlyy wants to merge 2 commits intocalcom:mainfrom
anglerfishlyy:patch-1

Conversation

@anglerfishlyy
Copy link

Summary of changes

  • Updated the assignment flow to support inviting people by email.

  • If an email does not belong to an existing team member, the user is automatically invited to the team as a Member.

  • Added support for multiple emails, using comma-separated input (user1@example.com, user2@example.com).

  • Updated frontend and backend to handle new host format { email, isPending } in addition to { userId }.

Why this is useful

  • Makes it easier to quickly assign event-types to new collaborators without requiring them to be manually added to the team first.

  • Supports inviting multiple participants in a single step, improving workflow efficiency.

Notes

  • This PR only covers the basic invitation + assignment flow.
  • CI should validate tests, but feedback on edge cases (e.g., invalid emails, already invited users) would be appreciated.

Bounty

/claim #13532

@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Aug 24, 2025
@graphite-app graphite-app bot requested a review from a team August 24, 2025 13:33
@vercel
Copy link

vercel bot commented Aug 24, 2025

@anglerfishlyy is attempting to deploy a commit to the cal Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 24, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The change introduces utilities to parse and normalize comma/space-separated email hosts, deduplicate entries by userId/email, and lowercase emails. Host handling now supports both userId-based and email-based entries via a unified mapToHost function, preserving existing priority/weight and scheduleId (fallback to defaultScheduleId). onChange paths normalize via email expansion; onActive paths map selected team members into hosts for both Fixed and RoundRobin modes. The UI is simplified to a single AddMembersWithSwitch flow for both modes. formState usage is reduced where unnecessary. No exported/public signatures are modified.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@dosubot dosubot bot added event-types area: event types, event-types ✨ feature New feature or request labels Aug 24, 2025
@graphite-app
Copy link

graphite-app bot commented Aug 24, 2025

Graphite Automations

"Add consumer team as reviewer" took an action on this PR • (08/24/25)

1 reviewer was added to this PR based on Keith Williams's automation.

"Add community label" took an action on this PR • (08/24/25)

1 label was added to this PR based on Keith Williams's automation.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx (3)

311-311: Undefined handler: handleFixedHostsToggle will cause a compile-time error

The handler referenced in onCheckedChange is not defined/imported anywhere in this file.

Apply this minimal fix (adjust behavior if more complex logic is required):

-          onCheckedChange={handleFixedHostsToggle}
+          onCheckedChange={(checked) => setIsDisabled(checked)}

740-755: updatedHosts only reconciles by userId; extend to email-based hosts to preserve scheduleId/groupId

When adding email hosts, scheduleId/groupId won’t be preserved because reconciliation only checks userId. Match on email (case-insensitive) as well.

Apply:

-    return changedHosts.map((newValue) => {
-      const existingHost = existingHosts.find((host: Host) => host.userId === newValue.userId);
+    return changedHosts.map((newValue) => {
+      const existingHost = existingHosts.find((host: Host & { email?: string }) => {
+        if (newValue.userId && host.userId) return host.userId === newValue.userId;
+        if ((newValue as any).email && host.email)
+          return host.email.toLowerCase() === (newValue as any).email.toLowerCase();
+        return false;
+      });

463-484: RoundRobin handleMembersActivation also overwrites fixed hosts and ignores email members

Even though this block wasn’t newly added, it now conflicts with the new email-based flow.

  • Preserve fixed hosts when activating members.
  • Use the same mapToHost helper to support email entries and consistent defaults.

Proposed direction:

-    setValue(
-      "hosts",
-      teamMembers.map((teamMember) => {
-        const host = currentHosts.find((host) => host.userId === parseInt(teamMember.value, 10));
-        return {
-          isFixed: false,
-          userId: parseInt(teamMember.value, 10),
-          priority: host?.priority ?? 2,
-          weight: host?.weight ?? 100,
-          scheduleId: host?.scheduleId || teamMember.defaultScheduleId,
-          groupId: host?.groupId || groupId,
-        };
-      }),
-      { shouldDirty: true }
-    );
+    const fixedHosts = currentHosts.filter((h) => h.isFixed);
+    const rrHosts = teamMembers.map((tm) => ({ ...mapToHost(tm, false, currentHosts), groupId }));
+    setValue("hosts", [...fixedHosts, ...rrHosts], { shouldDirty: true });

Also replace || with ?? for scheduleId if you keep the inline mapping.

🧹 Nitpick comments (4)
packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx (4)

79-100: expandCommaSeparatedEmailHosts: add guardrails and clarity

  • Consider a hard cap on expansions to avoid pathological inputs (e.g., 50 addresses per entry).
  • Avoid setting userId: undefined; once Host type is updated, simply omit userId for email entries.
  • Clarify deduping scope: current dedupe ignores groupId; confirm whether the same email is allowed across different RR groups.

Example cap:

+const MAX_EMAILS_PER_ENTRY = 50;
 ...
-      for (const e of emails) {
+      for (const e of emails.slice(0, MAX_EMAILS_PER_ENTRY)) {
         expanded.push({
           ...h,
           email: e,
           isPending: true,
-          userId: undefined,
           scheduleId: null,
         });
       }

If duplicates should be allowed across different groups, include groupId in the dedupe key.


39-41: EMAIL_REGEX is OK; consider minor robustness tweaks

  • You already lowercase later; no need for /i, but consider unicode domain names in the future (IDNA). For now, this ASCII-focused regex is fine.
  • Keep resetting lastIndex as you do in parseEmails.

42-53: parseEmails: efficient and deduplicates; add a soft limit and early exit on large inputs

To prevent accidental large pastes from ballooning allocations, add a max token limit and bail once reached.

Proposed tweak:

-const parseEmails = (input: string): string[] => {
+const parseEmails = (input: string, max = 200): string[] => {
   if (!input) return [];
   const found = new Set<string>();
   let m: RegExpExecArray | null;
   EMAIL_REGEX.lastIndex = 0;
   while ((m = EMAIL_REGEX.exec(input)) !== null) {
     const email = m[1].trim().toLowerCase();
     if (email) found.add(email);
+    if (found.size >= max) break;
   }
   return Array.from(found);
};

189-196: Localize link text

The “Learn more” label should use t() per guidelines. You’re already using ServerTrans; either move the label inside the translation string or wrap it with t().

Example:

-        Learn more
+        {t("learn_more")}

Or include the text in the i18n string and keep the component as an empty anchor.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d2f8076 and 72c7e77.

📒 Files selected for processing (1)
  • packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx (8 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Always use t() for text localization in frontend code; direct text embedding should trigger a warning

Files:

  • packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx
🧬 Code graph analysis (1)
packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx (2)
packages/features/eventtypes/lib/types.ts (3)
  • Host (27-34)
  • TeamMember (35-41)
  • FormValues (78-174)
packages/features/eventtypes/components/AddMembersWithSwitch.tsx (1)
  • AddMembersWithSwitch (245-349)
🔇 Additional comments (1)
packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx (1)

283-283: Applying expandCommaSeparatedEmailHosts in onChange is the right place

Good normalization point; this keeps the rest of the code working with fully expanded, deduped hosts.

Also applies to: 322-323, 499-500, 611-617

@anglerfishlyy anglerfishlyy marked this pull request as draft August 24, 2025 14:33
@anglerfishlyy
Copy link
Author

@coderabbitai , can you explain this pull request and the changes that i have to make to it for fixing #13532

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 26, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

I'll help you understand your PR and what changes might be needed to fix the issue. Let me first gather information about the issue you're trying to resolve.

@anglerfishlyy, based on my analysis of issue #13532 and your PR, here's what you're working on and what might need attention:

The Issue You're Solving

Issue #13532 requests the ability to "Allow users to assign event-types to emails directly instead of having to invite them to the team first." This would streamline the workflow by letting users assign event types to people via email without a separate invitation step.

Your PR Changes Explained

Your PR makes significant changes to support email-based host assignments:

Key Changes:

  1. Email Parsing Infrastructure: Added EMAIL_REGEX, parseEmails(), and expandCommaSeparatedEmailHosts() to handle multiple comma-separated emails
  2. Dual Host Format: Extended host handling to support both { userId } (existing) and { email, isPending } (new) formats
  3. Host Mapping Logic: Created mapToHost() function to convert team members to hosts, treating numeric values as userIds and non-numeric as emails
  4. UI Simplification: Replaced complex group-based UI with streamlined AddMembersWithSwitch components

What Your Code Does:

  • Parses comma-separated emails like "user1@example.com, user2@example.com"
  • Creates pending hosts for email addresses that don't correspond to existing team members
  • Maintains existing functionality for userId-based assignments
  • Deduplicates hosts to prevent duplicates

Potential Areas to Review/Fix

Based on the code analysis, here are areas that might need attention:

  1. Email Validation: Your EMAIL_REGEX is quite permissive. Consider if you need stricter validation:

    const EMAIL_REGEX = /[^\s,]+@[^\s,]+\.[^\s,]+/g;
  2. Error Handling: The code doesn't seem to handle invalid email formats or network errors when inviting users. You might want to add:

    • Invalid email feedback
    • Duplicate email handling
    • API error handling for invitations
  3. Backend Integration: Ensure your backend API can handle the new { email, isPending } format. The frontend changes look good, but verify the backend processes these correctly.

  4. User Experience: Consider adding:

    • Loading states while sending invitations
    • Success/error messages for invitations
    • Clear indication of pending vs confirmed hosts
  5. Edge Cases: Test scenarios like:

    • Already invited users
    • Invalid email formats
    • Mixed valid/invalid emails
    • Network failures during invitation

Would you like me to examine any specific part of the code more closely or help you implement fixes for any of these areas?


🏁 Script executed:

#!/bin/bash
# Get details about issue #13532 to understand what needs to be fixed
gh issue view 13532 --json title,body,state,labels,assignees,createdAt,updatedAt

Length of output: 2151


🏁 Script executed:

#!/bin/bash
# Also check if there are any comments or discussions on the issue
gh issue view 13532 --comments

Length of output: 3024


🏁 Script executed:

#!/bin/bash
# Let me also examine the main file that was changed to understand the current implementation
cat packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx

Length of output: 37207


@anglerfishlyy, now I have a complete picture of your PR and the issue you're solving. Here's a comprehensive breakdown:

Understanding Issue #13532

The issue requests three specific features:

  1. Allow emails in assignment - ✅ You've implemented this
  2. Invite person if not in team yet (as Member) - ⚠️ Frontend ready, backend integration needed
  3. Allow multiple comma-separated emails - ✅ You've implemented this

Your PR Implementation Analysis

✅ What's Working Well

Email Parsing Infrastructure:

const EMAIL_REGEX = /(?:^|[\s,;])([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[A-Za-z]{2,})(?=$|[\s,;])/g;
const parseEmails = (input: string): string[] => {
  // Handles multiple comma/semicolon/space separated emails
}
const expandCommaSeparatedEmailHosts = (incoming: Host[]): Host[] => {
  // Expands comma-separated emails into individual host entries
}

Host Format Support:

  • Successfully handles both { userId } and { email, isPending } formats
  • Proper deduplication logic
  • Smart mapping between team members and hosts

🚨 Critical Issues That Need Fixing

  1. Missing Function Definition:
// Line 218 - This function is called but not defined
onCheckedChange={handleFixedHostsToggle}

You need to add this function:

const handleFixedHostsToggle = useCallback((checked: boolean) => {
  setIsDisabled(checked);
  if (!checked) {
    // Clear fixed hosts when disabling
    const allHosts = getValues("hosts");
    const nonFixedHosts = allHosts.filter((host) => !host.isFixed);
    setValue("hosts", nonFixedHosts, { shouldDirty: true });
  }
}, [getValues, setValue, setIsDisabled]);
  1. Backend Integration Missing:
    Your frontend creates { email, isPending } hosts, but you need to ensure your backend:
  • Accepts this format in the API
  • Sends team invitations for email-based hosts
  • Handles the Member role assignment
  • Returns proper success/error responses
  1. Error Handling:
    No validation for invalid emails or API failures. Add:
const validateEmail = (email: string): boolean => {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
};

🔧 Specific Changes Needed

  1. Add the missing function (Line 218)
  2. Improve email validation - Your current regex is quite permissive
  3. Add error states for failed invitations
  4. Backend API integration for sending invites
  5. User feedback for successful invitations

🎯 To Complete the Issue Requirements

You need to verify/implement:

Backend Changes (check if these exist):

API Integration:

  • Endpoint to invite users by email to team
  • Proper error handling for duplicate/invalid emails
  • Success responses for invitation status

Would you like me to help you identify exactly what backend changes are needed, or would you prefer guidance on fixing the specific frontend issues first?


🏁 Script executed:

# Check if backend handles email-based hosts
rg -A 10 -B 5 "isPending.*email" --type ts

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Let's check if there are any backend API files that handle host assignments
rg -l "hosts.*email\|email.*hosts" --type ts -g "!**/test/**" -g "!**/node_modules/**"

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Let's search more broadly for backend files related to event types and hosts
rg -l "Host\|hosts" --type ts -g "**/api/**" -g "**/trpc/**" -g "**/lib/**" | head -20

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Look for API routes or TRPC procedures related to event types
fd -e ts -e js "eventtype\|event-type" | grep -E "(api|trpc|lib)" | head -10

Length of output: 78


🏁 Script executed:

#!/bin/bash
# Search for team invitation related code
rg -l "invite.*team\|team.*invite" --type ts | head -10

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Look for the Host type definition to understand the expected backend format
rg -A 5 -B 5 "type.*Host\|interface.*Host" --type ts

Length of output: 54


🏁 Script executed:

#!/bin/bash
# Check for existing isPending usage in the codebase
rg -n "isPending" --type ts | head -20

Length of output: 2604


🏁 Script executed:

#!/bin/bash
# Let's search for the type definitions more broadly
rg -n "Host.*=" --type ts | head -10

Length of output: 1210


🏁 Script executed:

#!/bin/bash
# Look for API or backend files in a different way
find . -path "*/node_modules" -prune -o -name "*.ts" -path "*/api/*" -print | head -10

Length of output: 513


🏁 Script executed:

#!/bin/bash
# Search for TRPC procedures or routes
find . -path "*/node_modules" -prune -o -name "*.ts" -path "*/trpc/*" -print | head -10

Length of output: 546


🏁 Script executed:

#!/bin/bash
# Look for the types file that likely contains Host definition
fd types.ts | head -5

Length of output: 222


🏁 Script executed:

#!/bin/bash
# Check what's imported in the main file to understand Host type
grep -n "import.*Host\|Host.*from" packages/features/eventtypes/components/tabs/assignment/EventTeamAssignmentTab.tsx

Length of output: 206


🏁 Script executed:

#!/bin/bash
# Let's check the types file that's imported to understand the Host type
cat packages/features/eventtypes/lib/types.ts

Length of output: 7696


🏁 Script executed:

#!/bin/bash
# Search for event type related TRPC procedures
rg -l "eventType\|event-type" ./apps/web/pages/api/trpc/ | head -5

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Look for team related TRPC procedures
find ./apps/web/pages/api/trpc -name "*team*" -o -name "*member*" | head -5

Length of output: 109


🏁 Script executed:

#!/bin/bash
# Search for Host type usage in backend files
rg -n "Host" ./apps/web/pages/api/trpc/eventTypes/[trpc].ts 2>/dev/null | head -5

Length of output: 71


🏁 Script executed:

#!/bin/bash
# Let's find the eventTypes TRPC file specifically
find . -path "*/trpc/*eventType*" -name "*.ts" | head -5

Length of output: 383


Based on the analysis so far, I can now provide you with a comprehensive understanding of what needs to be fixed in your PR.

🚨 Critical Issue Discovered

Looking at the Host type definition in packages/features/eventtypes/lib/types.ts, I can see the problem:

export type Host = {
  isFixed: boolean;
  userId: number;        // ❌ This is required, not optional
  priority: number;
  weight: number;
  scheduleId?: number | null;
  groupId: string | null;
};

Your PR is trying to create hosts like { email, isPending }, but the Host type doesn't support this! This is why your frontend changes won't work properly with the backend.

🔧 Required Fixes

1. Update the Host Type Definition

First, you need to modify the Host type to support email-based hosts:

// In packages/features/eventtypes/lib/types.ts
export type Host = {
  isFixed: boolean;
  userId?: number;           // Make optional
  email?: string;           // Add email support
  isPending?: boolean;      // Add pending status
  priority: number;
  weight: number;
  scheduleId?: number | null;
  groupId: string | null;
};

2. Fix the Missing Function

Add the missing handleFixedHostsToggle function in your component:

const handleFixedHostsToggle = useCallback((checked: boolean) => {
  setIsDisabled(checked);
  if (!checked) {
    // Clear fixed hosts when disabling
    const allHosts = getValues("hosts");
    const nonFixedHosts = allHosts.filter((host) => !host.isFixed);
    setValue("hosts", nonFixedHosts, { shouldDirty: true });
  }
}, [getValues, setValue, setIsDisabled]);

Let me check the backend structure to understand the complete picture:

3. Backend Integration Requirements

Based on the issue requirements, your backend needs to:

  1. Accept the new Host format with email/isPending fields
  2. Send team invitations when processing email-based hosts
  3. Assign Member role to invited users
  4. Handle validation for invalid emails and duplicate invitations

4. Testing Your Changes

To test if your changes work:

  1. Try adding user@example.com in the assignment field
  2. Try adding user1@example.com, user2@example.com
  3. Check if the backend processes these correctly
  4. Verify team invitations are sent

5. Next Steps

You should:

  1. Fix the Host type definition first
  2. Add the missing function
  3. Test the frontend with the type fixes
  4. Check if backend changes are needed for processing email-based hosts
  5. Implement team invitation logic if not already present

Would you like me to help you identify the specific backend files that need updating, or would you prefer to start with the frontend fixes first?


🏁 Script executed:

#!/bin/bash
# Let's check the TRPC router for eventTypes to understand the API structure
cat packages/trpc/server/routers/viewer/eventTypes/_router.ts

Length of output: 6048


🏁 Script executed:

#!/bin/bash
# Let's also check for team invitation functionality
find ./packages/trpc/server/routers -name "*team*" -type d | head -5

Length of output: 231


🏁 Script executed:

#!/bin/bash
# Look at the current Host type definition and see if email/isPending is supported
rg -A 10 -B 5 "type Host" packages/features/eventtypes/lib/types.ts

Length of output: 557


🏁 Script executed:

#!/bin/bash
# Check if there are any existing team invitation handlers
rg -l "invite.*Member\|addMember\|inviteMember" packages/trpc/server/routers/ -r

Length of output: 496


🏁 Script executed:

#!/bin/bash
# Check the eventTypes update handler to see what it expects
find packages/trpc/server/routers -name "*eventType*" -name "*.handler.ts" | head -5

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Look for team member invitation functionality
rg -l "inviteUser\|addTeamMember\|inviteMember" packages/trpc/server/routers/ -r

Length of output: 496


🏁 Script executed:

#!/bin/bash
# Look for team member invitation functionality - fix the command
rg -l "inviteUser\|addTeamMember\|inviteMember" packages/trpc/server/routers/

Length of output: 79


🏁 Script executed:

#!/bin/bash
# Check the update handler for event types
cat packages/trpc/server/routers/viewer/eventTypes/update.handler.ts

Length of output: 22696


🏁 Script executed:

#!/bin/bash
# Look at team invitation functionality
ls packages/trpc/server/routers/viewer/teams/

Length of output: 1886


🏁 Script executed:

#!/bin/bash
# Check if there's an invite functionality in teams
rg -l "invite" packages/trpc/server/routers/viewer/teams/

Length of output: 874


🏁 Script executed:

#!/bin/bash
# Look at the update schema to understand what the API expects
cat packages/trpc/server/routers/viewer/eventTypes/update.schema.ts

Length of output: 166


Now I have a complete picture! Let me provide you with the definitive analysis and fixes needed for your PR.

🎯 Complete Analysis: What Your PR Needs

The Problem

Your PR has the right approach but there are critical type and backend integration issues:

  1. ❌ Type Mismatch: The Host type only supports userId: number, but you're trying to create hosts with email and isPending properties that don't exist in the type definition.

  2. ❌ Backend Doesn't Support Email Hosts: Looking at the update.handler.ts, the backend expects all hosts to have userId and validates them against existing team members:

// Line 464-470: Backend validation
if (!hosts.every((host) => teamMemberIds.includes(host.userId)) && !eventType.team?.parentId) {
  throw new TRPCError({
    code: "FORBIDDEN",
  });
}

🚨 Critical Fixes Required

Fix #1: Update the Host Type Definition

// In packages/features/eventtypes/lib/types.ts
export type Host = {
  isFixed: boolean;
  userId?: number;           // ✅ Make optional 
  email?: string;           // ✅ Add email support
  isPending?: boolean;      // ✅ Add pending status
  priority: number;
  weight: number;
  scheduleId?: number | null;
  groupId: string | null;
};

Fix #2: Add Missing Function

// In your EventTeamAssignmentTab.tsx, add this missing function:
const handleFixedHostsToggle = useCallback((checked: boolean) => {
  setIsDisabled(checked);
  if (!checked) {
    const allHosts = getValues("hosts");
    const nonFixedHosts = allHosts.filter((host: Host) => !host.isFixed);
    setValue("hosts", nonFixedHosts, { shouldDirty: true });
  }
}, [getValues, setValue, setIsDisabled]);

Fix #3: Backend Schema Updates

You need to update the backend schema to accept email-based hosts. The backend update handler needs to:

  1. Support the new Host format
  2. Process email-based hosts by inviting them to the team
  3. Handle the invitation flow

Based on the team invitation system I found (there's a complete inviteMember handler), you'll need to:

Backend Changes Required:

// In the update handler, modify the host processing logic
if (teamId && hosts) {
  // Separate userId-based and email-based hosts
  const userIdHosts = hosts.filter(host => host.userId);
  const emailHosts = hosts.filter(host => host.email && host.isPending);
  
  // Existing validation for userId hosts
  const teamMemberIds = await membershipRepo.listAcceptedTeamMemberIds({ teamId });
  if (!userIdHosts.every((host) => teamMemberIds.includes(host.userId)) && !eventType.team?.parentId) {
    throw new TRPCError({
      code: "FORBIDDEN",
    });
  }
  
  // Process email hosts - invite them to team
  for (const emailHost of emailHosts) {
    // Call the existing team invitation logic
    // This should invite the user and create a pending membership
  }
  
  // Update the host creation logic to handle both types
}

Fix #4: Integration with Team Invitations

You need to integrate with the existing inviteMember functionality:

🎯 Your Action Plan

Immediate Steps:

  1. Update Host type (Fix The getIntegrations function runs repeatedly due to state polling #1)
  2. Add missing function (Fix When navigating to a calendso user that does not exist, return notFound. #2)
  3. Test frontend changes with type fixes

Backend Integration Steps:

  1. Update backend validation to handle email hosts
  2. Integrate team invitation flow for email-based hosts
  3. Handle success/error responses for invitations

Testing:

  1. Test with user@example.com
  2. Test with user1@example.com, user2@example.com
  3. Verify team invitations are sent
  4. Check error handling for invalid emails

Would you like me to help you implement any of these specific fixes, or do you want to tackle them in a particular order?


🏁 Script executed:

#!/bin/bash
# Look at the types file to understand the schema
cat packages/trpc/server/routers/viewer/eventTypes/types.ts

Length of output: 4260


🏁 Script executed:

#!/bash
# Check the team invite handler to see how team invitations work
cat packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts

Length of output: 9654


🏁 Script executed:

#!/bin/bash
# Let's check the invitation handler structure
head -50 packages/trpc/server/routers/viewer/teams/inviteMember/inviteMember.handler.ts

Length of output: 1752


Perfect! Now I have the complete picture. Your PR is very close to working, but there are some critical backend schema and integration issues. Let me give you the definitive solution.

🎯 The Complete Fix Guide

Problem Analysis

  1. Backend Schema Issue: The hostSchema in types.ts requires userId: z.number() (not optional)
  2. Backend Validation: The update handler validates all hosts must have existing userId
  3. Missing Integration: No connection between email-based hosts and team invitations

🔧 Fix #1: Update Backend Schema

// In packages/trpc/server/routers/viewer/eventTypes/types.ts
// Replace the existing hostSchema:
const hostSchema = z.object({
  userId: z.number().optional(),        // ✅ Make optional
  email: z.string().email().optional(), // ✅ Add email support  
  isPending: z.boolean().optional(),    // ✅ Add pending status
  profileId: z.number().or(z.null()).optional(),
  isFixed: z.boolean().optional(),
  priority: z.number().min(0).max(4).optional().nullable(),
  weight: z.number().min(0).optional().nullable(),
  scheduleId: z.number().optional().nullable(),
  groupId: z.string().optional().nullable(),
}).refine((data) => {
  // ✅ Ensure either userId OR email is provided
  return data.userId !== undefined || data.email !== undefined;
}, {
  message: "Either userId or email must be provided",
});

🔧 Fix #2: Update Frontend Host Type

// In packages/features/eventtypes/lib/types.ts
export type Host = {
  isFixed: boolean;
  userId?: number;           // ✅ Make optional
  email?: string;           // ✅ Add email support
  isPending?: boolean;      // ✅ Add pending status
  priority: number;
  weight: number;
  scheduleId?: number | null;
  groupId: string | null;
};

🔧 Fix #3: Add Missing Function (Frontend)

// In your EventTeamAssignmentTab.tsx, add this function:
const handleFixedHostsToggle = useCallback((checked: boolean) => {
  setIsDisabled(checked);
  if (!checked) {
    const allHosts = getValues("hosts");
    const nonFixedHosts = allHosts.filter((host: Host) => !host.isFixed);
    setValue("hosts", nonFixedHosts, { shouldDirty: true });
  }
}, [getValues, setValue, setIsDisabled]);

🔧 Fix #4: Update Backend Handler (Most Important)

In packages/trpc/server/routers/viewer/eventTypes/update.handler.ts, replace the hosts processing section (around line 464) with:

if (teamId && hosts) {
  // Separate userId-based and email-based hosts
  const userIdHosts = hosts.filter(host => host.userId);
  const emailHosts = hosts.filter(host => host.email && host.isPending);
  
  // Existing validation for userId hosts only
  const teamMemberIds = await membershipRepo.listAcceptedTeamMemberIds({ teamId });
  if (!userIdHosts.every((host) => teamMemberIds.includes(host.userId)) && !eventType.team?.parentId) {
    throw new TRPCError({
      code: "FORBIDDEN",
    });
  }

  // ✅ Process email-based hosts - invite them to team as Members
  if (emailHosts.length > 0) {
    // Import the invitation function
    const { inviteMembersWithNoInviterPermissionCheck } = await import("../teams/inviteMember/inviteMember.handler");
    
    try {
      await inviteMembersWithNoInviterPermissionCheck({
        inviterName: ctx.user.name,
        teamId: teamId,
        language: ctx.user.locale || "en",
        creationSource: "INVITATION" as any,
        orgSlug: eventType.team?.slug || null,
        invitations: emailHosts.map(host => ({
          usernameOrEmail: host.email!,
          role: MembershipRole.MEMBER
        })),
        isDirectUserAction: false, // Since this is automated
      });
    } catch (error) {
      // Log the error but don't fail the entire operation
      console.warn("Failed to invite some email hosts:", error);
    }
  }

  // Update host processing to handle both types
  const allHosts = [...userIdHosts, ...emailHosts];
  const oldHostsSet = new Set(eventType.hosts.map((oldHost) => oldHost.userId));
  const newHostsSet = new Set(allHosts.filter(h => h.userId).map((host) => host.userId));

  const existingHosts = allHosts.filter((newHost) => newHost.userId && oldHostsSet.has(newHost.userId));
  const newHosts = allHosts.filter((newHost) => !newHost.userId || !oldHostsSet.has(newHost.userId));
  const removedHosts = eventType.hosts.filter((oldHost) => !newHostsSet.has(oldHost.userId));

  data.hosts = {
    deleteMany: {
      OR: removedHosts.map((host) => ({
        userId: host.userId,
        eventTypeId: id,
      })),
    },
    create: newHosts.filter(host => host.userId).map((host) => {
      return {
        userId: host.userId!,
        isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
        priority: host.priority ?? 2,
        weight: host.weight ?? 100,
        groupId: host.groupId,
      };
    }),
    update: existingHosts.map((host) => ({
      where: {
        userId_eventTypeId: {
          userId: host.userId!,
          eventTypeId: id,
        },
      },
      data: {
        isFixed: data.schedulingType === SchedulingType.COLLECTIVE || host.isFixed,
        priority: host.priority ?? 2,
        weight: host.weight ?? 100,
        scheduleId: host.scheduleId ?? null,
        groupId: host.groupId,
      },
    })),
  };
}

🔧 Fix #5: Add Required Import

At the top of update.handler.ts, add:

import { MembershipRole } from "@calcom/prisma/enums";

🧪 Testing Your Changes

  1. Test single email: Try user@example.com
  2. Test multiple emails: Try user1@example.com, user2@example.com
  3. Test mixed: Try existing user IDs and new emails together
  4. Verify invitations: Check if team invitations are actually sent

🎯 Your Implementation Order

  1. Start with Fix When navigating to a calendso user that does not exist, return notFound. #2 (Frontend Host type) - This will fix your type errors
  2. Add Fix Fixed some minor bugs that caused console errors #3 (Missing function) - This will fix the runtime error
  3. Test frontend changes - Make sure the UI works
  4. Then implement Fix The getIntegrations function runs repeatedly due to state polling #1 & Fix Unable to login if running on custom port #4 (Backend changes) - For full functionality

💡 Why This Works

  • Frontend: Supports both userId and email hosts with proper type safety
  • Backend Schema: Accepts both formats with validation
  • Team Invitations: Automatically invites email-based hosts as Members
  • Error Handling: Gracefully handles invitation failures
  • Database: Only stores hosts with userId (pending invitations become real hosts when accepted)

The key insight is that email-based hosts trigger team invitations but don't get stored as hosts until the invitation is accepted. This maintains data integrity while enabling the requested functionality.

Start with the frontend fixes first, then move to backend integration. Let me know if you need help with any specific part!

@kart1ka
Copy link
Contributor

kart1ka commented Aug 26, 2025

@anglerfishlyy I see you have opened two PRs (the other one being #23314) for the same issue. Can you please close the one which is not relevant?

@anglerfishlyy anglerfishlyy deleted the patch-1 branch August 27, 2025 07:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🙋 Bounty claim community Created by Linear-GitHub Sync event-types area: event types, event-types ✨ feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants