Skip to content

Conversation

@visz11
Copy link

@visz11 visz11 commented Dec 15, 2025

CodeAnt-AI Description

Add self-hosted admin email consent step and profile banner

What Changed

  • New optional setup step for self-hosted installs that asks for an admin email and two consent choices (product changelog and marketing); the email is prefilled from the admin account just created and can be skipped
  • Consent responses are sent to Cal.com's headless router so admins can opt in without leaving the setup flow
  • A profile banner appears for self-hosted admins linking back to the new setup step so admins can update preferences later

Impact

✅ Collect admin emails from self-hosters
✅ Clearer opt-in for product changelogs and marketing
✅ Easier update of admin preferences from profile

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added email consent collection step in the setup process for self-hosted deployments
    • Introduced admin email preference banner in account settings
    • Added options to manage product changelog and marketing email preferences

✏️ Tip: You can customize this high-level summary in your review settings.

devin-ai-integration bot and others added 4 commits December 8, 2025 16:12
- Add new step 2 for self-hosters (!IS_CALCOM) to collect optional admin email
- Prefill email from step 1 admin user creation
- Add checkboxes for product changelog and marketing consent
- Submit to Cal.com form via headless routing API
- Add skip button to allow users to bypass this step
- Add translation strings for new UI elements

Co-Authored-By: [email protected] <[email protected]>
@codeant-ai
Copy link

codeant-ai bot commented Dec 15, 2025

CodeAnt AI is reviewing your PR.


Thanks for using CodeAnt! 🎉

We're free for open-source projects. if you're enjoying it, help us grow by sharing.

Share on X ·
Reddit ·
LinkedIn

@gemini-code-assist
Copy link

Summary of Changes

Hello @visz11, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a crucial feature for self-hosted Cal.com instances, enabling administrators to provide their email address and consent to receive important updates, such as product changelogs and security advisories, directly from Cal.com. This is achieved by integrating a new email consent form into the initial setup wizard and providing a persistent banner in the admin's profile settings for managing these preferences, ensuring self-hosters stay informed about their instance.

Highlights

  • Admin Email Consent Form: A new form (AdminEmailConsent.tsx) has been added to allow self-hosted administrators to provide their email for receiving product changelogs and marketing communications.
  • Setup Wizard Integration: The email consent form is now a conditional step in the initial setup wizard for self-hosted instances, appearing after the admin user creation.
  • Admin Email Consent Banner: A new banner (AdminEmailConsentBanner.tsx) is displayed in the account settings for self-hosted administrators, providing a quick link to update their email preferences.
  • Internationalization Support: New localization keys have been added to common.json to support the new email consent UI in multiple languages.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Dec 15, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This change introduces an email consent workflow for admin users during setup. It adds a new AdminEmailConsent component for collecting admin email preferences, updates the AdminUser component's callback to pass email, integrates email consent into the setup flow for self-hosted instances, and adds a banner in account settings to redirect users to complete email consent. Localization strings are added for the new UI elements.

Changes

Cohort / File(s) Summary
New email consent components
apps/web/components/setup/AdminEmailConsent.tsx, apps/web/modules/settings/my-account/components/AdminEmailConsentBanner.tsx
Introduces AdminEmailConsent form component for collecting admin email and product/marketing consents during setup, and AdminEmailConsentBanner banner component that links to email consent step from account settings.
Setup flow integration
apps/web/components/setup/AdminUser.tsx, apps/web/modules/auth/setup-view.tsx
Updates AdminUser's onSuccess callback signature to accept optional email parameter and pass user email on success. Integrates AdminEmailConsent as a conditional step in the setup wizard for self-hosted deployments, with navigation logic based on license state.
Profile settings
apps/web/modules/settings/my-account/profile-view.tsx
Adds conditionally rendered AdminEmailConsentBanner for self-hosted admin users. Renames ProfileForm props isFallbackImg and userOrganization to \_isFallbackImg and \_userOrganization.
Localization
apps/web/public/static/locales/en/common.json
Adds translation keys for email consent feature: stay\_informed, stay\_informed\_description, self\_hosted\_admin\_email\_description, admin\_email\_label, product\_changelog\_consent, marketing\_consent, update\_preferences.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AdminUser
    participant AdminEmailConsent
    participant SetupFlow
    participant LicenseStep
    
    User->>AdminUser: Complete admin setup
    activate AdminUser
    AdminUser->>AdminUser: Validate admin details
    AdminUser->>SetupFlow: onSuccess(email)
    deactivate AdminUser
    
    activate SetupFlow
    SetupFlow->>SetupFlow: Store admin email in state
    alt showAdminEmailConsent (self-hosted)
        SetupFlow->>AdminEmailConsent: Navigate to email consent step
        activate AdminEmailConsent
        User->>AdminEmailConsent: Submit email & consent preferences<br/>or skip
        AdminEmailConsent->>SetupFlow: onSubmit/onSkip
        deactivate AdminEmailConsent
    end
    
    SetupFlow->>LicenseStep: Navigate to next step<br/>(license or app setup)
    deactivate SetupFlow
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • setup-view.tsx: Review the conditional logic for showAdminEmailConsent and the step navigation paths (advance once vs. twice depending on license state)
  • AdminUser.tsx: Verify the callback signature change is consistently applied and email is correctly passed
  • profile-view.tsx: Check the prop renames (\_isFallbackImg, \_userOrganization) are correctly threaded through ProfileForm usage and the admin banner conditional logic works as intended

Poem

🐰 A consent form hops into the setup,
where admins share their emails to stay in touch—
newsletters and changelogs bound in trust,
self-hosted wizards now have a choice robust,
preferences preserved, one bounce at a time! 📧

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title is vague and includes a ticket/branch identifier rather than clearly summarizing the main change; it uses non-descriptive phrasing that doesn't convey the primary purpose of the changeset. Revise the title to clearly describe the main change, such as 'Add admin email consent flow for self-hosted deployments' or 'Implement email consent component in admin setup wizard', removing branch identifiers.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/self-hoster-admin-email-consent-1765209823

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

Comment @coderabbitai help to get the list of available commands and usage tips.

@visz11
Copy link
Author

visz11 commented Dec 15, 2025

@refacto-visz

@codeant-ai codeant-ai bot added the size:L This PR changes 100-499 lines, ignoring generated files label Dec 15, 2025
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an admin email consent step for self-hosted instances, which is a valuable addition for keeping administrators informed. The implementation is solid, creating a new component for the consent form and integrating it into the setup wizard. A banner is also added to the admin's profile page to allow for future preference updates. My review includes a couple of suggestions to improve code quality by simplifying a form controller and removing duplicated logic in the setup wizard.

Comment on lines +96 to +109
<Controller
name="email"
control={formMethods.control}
render={({ field: { onBlur, onChange, value } }) => (
<EmailField
label={t("admin_email_label")}
value={value || ""}
onBlur={onBlur}
onChange={(e) => onChange(e.target.value)}
className="my-0"
name="email"
/>
)}
/>

Choose a reason for hiding this comment

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

medium

The Controller for the EmailField can be simplified by spreading the field object from react-hook-form. This reduces boilerplate and improves readability by automatically passing props like onChange, onBlur, value, and name.

Suggested change
<Controller
name="email"
control={formMethods.control}
render={({ field: { onBlur, onChange, value } }) => (
<EmailField
label={t("admin_email_label")}
value={value || ""}
onBlur={onBlur}
onChange={(e) => onChange(e.target.value)}
className="my-0"
name="email"
/>
)}
/>
<Controller
name="email"
control={formMethods.control}
render={({ field }) => (
<EmailField
label={t("admin_email_label")}
{...field}
value={field.value || ""}
className="my-0"
/>
)}
/>

Comment on lines +90 to +116
return (
<AdminEmailConsent
id="wizard-step-2"
name="wizard-step-2"
defaultEmail={adminEmail}
onSubmit={() => {
setIsPending(true);
// After consent step, go to license step or apps step
if (props.hasValidLicense || hasPickedAGPLv3) {
nav.onNext();
nav.onNext(); // Skip license step
} else {
nav.onNext();
}
}}
onSkip={() => {
// After skipping, go to license step or apps step
if (props.hasValidLicense || hasPickedAGPLv3) {
nav.onNext();
nav.onNext(); // Skip license step
} else {
nav.onNext();
}
}}
onPrevStep={nav.onPrev}
/>
);

Choose a reason for hiding this comment

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

medium

The navigation logic within onSubmit and onSkip for the AdminEmailConsent component is duplicated. To improve maintainability and reduce redundancy, this logic should be extracted into a shared function.

        const handleNext = () => {
          // After consent step, go to license step or apps step
          if (props.hasValidLicense || hasPickedAGPLv3) {
            nav.onNext();
            nav.onNext(); // Skip license step
          } else {
            nav.onNext();
          }
        };
        return (
          <AdminEmailConsent
            id="wizard-step-2"
            name="wizard-step-2"
            defaultEmail={adminEmail}
            onSubmit={() => {
              setIsPending(true);
              handleNext();
            }}
            onSkip={handleNext}
            onPrevStep={nav.onPrev}
          />
        );

@refacto-visz
Copy link

refacto-visz bot commented Dec 15, 2025

Refacto PR Summary

Added admin email consent collection during self-hosted Cal.com setup to enable communication about security updates and product changes. The implementation integrates a new consent step in the setup wizard for self-hosted instances only, with optional marketing and changelog subscriptions.

Key Changes:

  • New AdminEmailConsent component with form validation for email and consent checkboxes
  • Modified setup wizard flow to include consent step after admin user creation (self-hosted only)
  • Added admin email consent banner in profile settings for existing self-hosted administrators
  • Email submission to headless router endpoint with consent preferences tracking
  • Conditional display logic using IS_CALCOM flag to differentiate hosted vs self-hosted instances

Change Highlights

Click to expand
  • AdminEmailConsent.tsx: New consent form with email validation and checkbox controls for product/marketing preferences
  • setup-view.tsx: Integrated consent step in setup wizard with conditional navigation logic
  • AdminUser.tsx: Modified to pass admin email to next setup step
  • profile-view.tsx: Added consent banner for existing self-hosted admin users
  • AdminEmailConsentBanner.tsx: Settings page banner component linking to consent preferences
  • common.json: Added localization strings for consent form labels and descriptions

Sequence Diagram

sequenceDiagram
    participant A as Admin User
    participant S as Setup Wizard
    participant C as Consent Form
    participant R as Router API
    participant P as Profile Settings
    
    A->>S: Complete admin user creation
    S->>S: Check IS_CALCOM flag
    S->>C: Navigate to consent step (self-hosted only)
    A->>C: Enter email and preferences
    C->>R: Submit to headless router endpoint
    R-->>C: Consent recorded
    C->>S: Continue to license/apps step
    
    Note over P: Later access via settings
    A->>P: Visit profile settings
    P->>P: Check admin role + self-hosted
    P->>A: Show consent banner if applicable
Loading

Testing Guide

Click to expand
  1. Self-hosted setup flow: Complete setup wizard, verify consent step appears after admin user creation with email pre-populated
  2. Consent form validation: Test email validation (invalid formats), checkbox interactions, and form submission with various consent combinations
  3. Skip functionality: Use skip button and verify navigation continues to license/apps step without errors
  4. Profile banner display: Login as admin on self-hosted instance, check profile settings for consent banner visibility
  5. Hosted instance exclusion: Test setup on Cal.com hosted version, confirm consent step is bypassed entirely

@refacto-visz
Copy link

refacto-visz bot commented Dec 15, 2025

Refacto is reviewing this PR. Please wait for the review comments to be posted.

import { CheckboxField, EmailField } from "@calcom/ui/components/form";

const FORM_ID = "d4fe10cb-6cb3-4c84-ae61-394a817c419e";
const HEADLESS_ROUTER_URL = "https://i.cal.com/router";
Copy link

Choose a reason for hiding this comment

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

Suggestion: The headless router URL is hardcoded to the production endpoint, which makes the behavior inflexible for different environments and forces all self-hosted instances to send data to that fixed URL instead of allowing configuration. [possible bug]

Severity Level: Critical 🚨

Suggested change
const HEADLESS_ROUTER_URL = "https://i.cal.com/router";
const HEADLESS_ROUTER_URL =
process.env.NEXT_PUBLIC_HEADLESS_ROUTER_URL ?? "https://i.cal.com/router";
Why it matters? ⭐

Hardcoding an external production URL in component code forces all deployments (including self-hosted) to contact that endpoint and prevents per-environment configuration. Replacing it with a NEXT_PUBLIC_ env fallback is a practical improvement for configurability and testing.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/web/components/setup/AdminEmailConsent.tsx
**Line:** 14:14
**Comment:**
	*Possible Bug: The headless router URL is hardcoded to the production endpoint, which makes the behavior inflexible for different environments and forces all self-hosted instances to send data to that fixed URL instead of allowing configuration.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +76 to +80
} catch (error) {
console.error("Failed to submit admin email consent:", error);
} finally {
setIsSubmitting(false);
onSubmit();
Copy link

Choose a reason for hiding this comment

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

Suggestion: If the consent submission request fails (for example due to network issues or the headless router being unreachable), the step callback is still invoked in the finally block, causing the setup wizard to advance as if consent was recorded even though the data was never sent successfully. [logic error]

Severity Level: Minor ⚠️

Suggested change
} catch (error) {
console.error("Failed to submit admin email consent:", error);
} finally {
setIsSubmitting(false);
onSubmit();
onSubmit();
} catch (error) {
console.error("Failed to submit admin email consent:", error);
} finally {
setIsSubmitting(false);
Why it matters? ⭐

The suggestion correctly highlights a logic issue: onSubmit() is invoked in the finally block so the wizard advances even when the network request fails. Moving onSubmit() into the successful path (after the await fetch) ensures the step only advances when the submission completed without throwing. This fixes a real UX/data-loss problem rather than being purely cosmetic.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/web/components/setup/AdminEmailConsent.tsx
**Line:** 76:80
**Comment:**
	*Logic Error: If the consent submission request fails (for example due to network issues or the headless router being unreachable), the step callback is still invoked in the `finally` block, causing the setup wizard to advance as if consent was recorded even though the data was never sent successfully.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +139 to +141
<Button type="button" color="secondary" onClick={onPrevStep}>
{t("prev_step")}
</Button>
Copy link

Choose a reason for hiding this comment

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

Suggestion: While the async consent submission is in progress, the "Prev step" and "Skip" buttons remain active, so a user can navigate away and then the pending submission callback will run later, causing inconsistent multi-step navigation (e.g. skipping or double-advancing steps). [race condition]

Severity Level: Minor ⚠️

Suggested change
<Button type="button" color="secondary" onClick={onPrevStep}>
{t("prev_step")}
</Button>
<Button type="button" color="secondary" onClick={onPrevStep} disabled={isSubmitting}>
{t("prev_step")}
</Button>
<Button type="button" color="minimal" onClick={handleSkip} disabled={isSubmitting}>
Why it matters? ⭐

This is a valid concurrency/race concern. While submit disables the submit button via its loading prop, Prev/Skip remain active and can let a user navigate away while an in-flight request later calls callbacks (or onSubmit) and produce inconsistent wizard state. Disabling Prev/Skip when isSubmitting prevents that class of race and is a minimal, sensible UX/safety fix.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/web/components/setup/AdminEmailConsent.tsx
**Line:** 139:141
**Comment:**
	*Race Condition: While the async consent submission is in progress, the "Prev step" and "Skip" buttons remain active, so a user can navigate away and then the pending submission callback will run later, causing inconsistent multi-step navigation (e.g. skipping or double-advancing steps).

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

);
const [adminEmail, setAdminEmail] = useState<string>("");

const showAdminEmailConsent = !IS_CALCOM;
Copy link

Choose a reason for hiding this comment

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

Suggestion: The admin email consent step is always added for self-hosters regardless of user count, which shifts the step indices and breaks the existing defaultStep logic that assumes the second step is the license selection and the third is the apps step; restricting the consent step to the initial admin-user flow keeps the wizard step indexes consistent with the constants. [logic error]

Severity Level: Minor ⚠️

Suggested change
const showAdminEmailConsent = !IS_CALCOM;
const showAdminEmailConsent = !IS_CALCOM && props.userCount === 0;
Why it matters? ⭐

The PR always inserts the AdminEmailConsent step for self-hosted installs (showAdminEmailConsent = !IS_CALCOM), regardless of props.userCount. defaultStep is computed earlier using numeric constants (ADMIN_USER = 1, LICENSE = 2, APPS = 3). If the consent step is pushed into the steps array between ADMIN_USER and LICENSE, the numeric defaults (2 and 3) no longer point at LICENSE/APPS respectively and will target the wrong step (e.g. point at the consent step). The suggested change (restrict consent to the initial admin-user flow) is a valid fix to keep step indices consistent or, alternatively, the defaultStep calculation should be adjusted to reflect dynamic insertion of steps. This is a real logic bug, not a cosmetic nit.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/web/modules/auth/setup-view.tsx
**Line:** 35:35
**Comment:**
	*Logic Error: The admin email consent step is always added for self-hosters regardless of user count, which shifts the step indices and breaks the existing `defaultStep` logic that assumes the second step is the license selection and the third is the apps step; restricting the consent step to the initial admin-user flow keeps the wizard step indexes consistent with the constants.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +92 to +93
id="wizard-step-2"
name="wizard-step-2"
Copy link

Choose a reason for hiding this comment

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

Suggestion: Both the admin email consent step and the license selection step use the same id and name ("wizard-step-2"), which can cause duplicate DOM IDs and ambiguous form field references; giving the consent step a distinct identifier avoids collisions. [possible bug]

Severity Level: Critical 🚨

Suggested change
id="wizard-step-2"
name="wizard-step-2"
id="wizard-step-2-consent"
name="wizard-step-2-consent"
Why it matters? ⭐

The consent step and the license selection step both use id/name "wizard-step-2" in the PR. If both components are rendered in the DOM (or during testing/SSR snapshots) this will produce duplicate DOM IDs and can cause ambiguous form submissions or accessibility issues. Giving the consent step a distinct id/name (or deriving them from the current steps length) prevents collisions. This is a valid, actionable fix.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/web/modules/auth/setup-view.tsx
**Line:** 92:93
**Comment:**
	*Possible Bug: Both the admin email consent step and the license selection step use the same `id` and `name` ("wizard-step-2"), which can cause duplicate DOM IDs and ambiguous form field references; giving the consent step a distinct identifier avoids collisions.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines 281 to +282
user={user}
userOrganization={user.organization}
_userOrganization={user.organization}
Copy link

Choose a reason for hiding this comment

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

Suggestion: The _isFallbackImg and _userOrganization props are passed into the ProfileForm component but never used, creating dead props that increase coupling and can confuse future maintainers about their purpose. [code quality]

Severity Level: Minor ⚠️

Suggested change
user={user}
userOrganization={user.organization}
_userOrganization={user.organization}
user={user}
Why it matters? ⭐

The props _isFallbackImg and _userOrganization are indeed passed from ProfileView into ProfileForm but are unused inside ProfileForm. Keeping them around creates dead surface area and can confuse future maintainers. Removing these from the caller (and the component signature) is a safe cleanup that reduces coupling and improves the component API.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/web/modules/settings/my-account/profile-view.tsx
**Line:** 281:282
**Comment:**
	*Code Quality: The `_isFallbackImg` and `_userOrganization` props are passed into the `ProfileForm` component but never used, creating dead props that increase coupling and can confuse future maintainers about their purpose.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines 519 to 529
defaultValues: FormValues;
onSubmit: (values: ExtendedFormValues) => void;
handleAddSecondaryEmail: () => void;
handleResendVerifyEmail: (email: string) => void;
handleAccountDisconnect: (values: ExtendedFormValues) => void;
extraField?: React.ReactNode;
isPending: boolean;
isFallbackImg: boolean;
_isFallbackImg: boolean;
user: RouterOutputs["viewer"]["me"]["get"];
userOrganization: RouterOutputs["viewer"]["me"]["get"]["organization"];
_userOrganization: RouterOutputs["viewer"]["me"]["get"]["organization"];
isCALIdentityProvider: boolean;
Copy link

Choose a reason for hiding this comment

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

Suggestion: The _isFallbackImg and _userOrganization properties are declared in the ProfileForm parameter list and its props type but never used inside the component, leading to unnecessary props that can get out of sync with callers and make the component API misleading. [code quality]

Severity Level: Minor ⚠️

Suggested change
user,
isCALIdentityProvider,
}: {
defaultValues: FormValues;
onSubmit: (values: ExtendedFormValues) => void;
handleAddSecondaryEmail: () => void;
handleResendVerifyEmail: (email: string) => void;
handleAccountDisconnect: (values: ExtendedFormValues) => void;
extraField?: React.ReactNode;
isPending: boolean;
user: RouterOutputs["viewer"]["me"]["get"];
Why it matters? ⭐

The props _isFallbackImg and _userOrganization are declared in the ProfileForm signature and its props type but they are never referenced inside the component. This makes the component API misleading. Removing them from the parameter list and the type definition is a straightforward, safe cleanup that prevents API drift and reduces maintenance burden.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/web/modules/settings/my-account/profile-view.tsx
**Line:** 519:529
**Comment:**
	*Code Quality: The `_isFallbackImg` and `_userOrganization` properties are declared in the `ProfileForm` parameter list and its props type but never used inside the component, leading to unnecessary props that can get out of sync with callers and make the component API misleading.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

};

const { data: usersAttributes, isPending: usersAttributesPending } =
const { data: usersAttributes, isPending: _usersAttributesPending } =
Copy link

Choose a reason for hiding this comment

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

Suggestion: The _usersAttributesPending value is destructured from the trpc.viewer.attributes.getByUserId.useQuery result but never used, which adds noise and can trigger lint warnings without providing any functional benefit. [code quality]

Severity Level: Minor ⚠️

Suggested change
const { data: usersAttributes, isPending: _usersAttributesPending } =
const { data: usersAttributes } =
Why it matters? ⭐

The query result includes isPending aliased to _usersAttributesPending, but that variable is never read. Removing the unused destructured binding reduces lint noise and is safe — the query's data is still used (usersAttributes). This is a small cleanup with no behavioral impact.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** apps/web/modules/settings/my-account/profile-view.tsx
**Line:** 618:618
**Comment:**
	*Code Quality: The `_usersAttributesPending` value is destructured from the `trpc.viewer.attributes.getByUserId.useQuery` result but never used, which adds noise and can trigger lint warnings without providing any functional benefit.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

@codeant-ai
Copy link

codeant-ai bot commented Dec 15, 2025

Nitpicks 🔍

🔒 No security issues identified
⚡ Recommended areas for review

  • Sensitive data in URL
    The admin email is appended to the request URL as a query parameter and sent via a GET request. This exposes the email in browser history, server logs, and referer headers. Consider using a POST with a request body or another mechanism that does not put the email in the URL.

  • Unverified sign-in result
    After creating the account the code awaits signIn(...) and immediately calls props.onSuccess(...). signIn can return an error object (or undefined) rather than throwing; the code should inspect the signIn result and only call onSuccess when sign-in actually succeeded.

  • no-cors hides errors
    Using fetch with mode "no-cors" yields an opaque response and suppresses CORS errors; the code still proceeds (onSubmit is called in finally), which can give false positives for successful submission. Validate behavior and consider handling failures differently or enable CORS on the endpoint.

  • Consent parameter format
    The code appends multiple "consent" query params when multiple checkboxes are selected. Verify that the headless router expects repeated keys rather than a single aggregated value (e.g., "consent=product,marketing").

  • Prop signature change
    The onSuccess prop signature was changed from () => void to (email?: string) => void. This can introduce type breaks for any other callers of AdminUser or AdminUserContainer that still expect the old signature. Verify all usages of AdminUser and AdminUserContainer are updated to accept the new signature, or consider making the prop optional to avoid breaking changes.

@codeant-ai
Copy link

codeant-ai bot commented Dec 15, 2025

CodeAnt AI finished reviewing your PR.

@refacto-visz
Copy link

refacto-visz bot commented Dec 15, 2025

Code Review: Admin Consent Setup Flow

PR Confidence Score: 🟨 4 / 5

👍 Well Done
Robust Optional Step

The new consent step correctly handles API failures using a try/catch/finally block, ensuring it does not block user setup.

Modular Wizard Extension

Adding the new consent step conditionally and as a self-contained component improves the setup wizard's modularity.

Proper Email Validation

Comprehensive email validation with regex patterns prevents malformed input.

📁 Selected files for review (6)
  • apps/web/components/setup/AdminEmailConsent.tsx
  • apps/web/components/setup/AdminUser.tsx
  • apps/web/modules/auth/setup-view.tsx
  • apps/web/modules/settings/my-account/components/AdminEmailConsentBanner.tsx
  • apps/web/modules/settings/my-account/profile-view.tsx
  • apps/web/public/static/locales/en/common.json
📝 Additional Comments
apps/web/modules/settings/my-account/components/AdminEmailConsentBanner.tsx (1)
Hardcoded Wizard Step URL

The navigation to a specific setup wizard step uses a hardcoded step=2 query parameter. This creates a tight coupling between this banner and the wizard's structure in setup-view.tsx. If the order or number of steps in the setup wizard changes, this link will become incorrect, leading to a broken user flow. This logic should be decoupled, for example by using a shared constant for step identifiers.

Standards:

  • Logic-Verification-Control-Flow
apps/web/components/setup/AdminEmailConsent.tsx (1)
Error Information Disclosure

Error logging may expose sensitive network information or API details in browser console. While low risk, this could provide reconnaissance information to attackers with access to admin browser sessions.

Standards:

  • CWE-209
  • OWASP-A09

params.append("consent", "marketing");
}

const url = `${HEADLESS_ROUTER_URL}?${params.toString()}`;
Copy link

Choose a reason for hiding this comment

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

Admin Email Exposure

Admin email addresses are transmitted via GET request URL parameters to external service. URLs are logged in server access logs, browser history, and referrer headers exposing sensitive admin contact information. This creates data exposure risk for self-hosted administrators.

Standards
  • CWE-200
  • OWASP-A02
  • NIST-SSDF-PW.1

Comment on lines +13 to +14
const FORM_ID = "d4fe10cb-6cb3-4c84-ae61-394a817c419e";
const HEADLESS_ROUTER_URL = "https://i.cal.com/router";
Copy link

Choose a reason for hiding this comment

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

External Data Transmission

Self-hosted admin data is transmitted to external Cal.com infrastructure without explicit user consent disclosure. This creates potential data sovereignty and privacy compliance issues for organizations with strict data residency requirements. Administrators may not realize their information leaves their infrastructure.

Standards
  • CWE-359
  • OWASP-A02
  • Compliance-GDPR

const router = useRouter();

const handleOpenAdminStep = () => {
router.push("/auth/setup?step=2");
Copy link

Choose a reason for hiding this comment

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

Incorrect Wizard Step Navigation

The banner navigates to step=2 in the setup wizard. However, the new admin consent step is dynamically inserted at index 1 of the wizard's step array in setup-view.tsx. This incorrect index will lead the user to the wrong step, breaking the intended navigation flow from the profile settings.

Standards
  • ISO-IEC-25010-Functional-Correctness-Appropriateness

Comment on lines +98 to +112
if (props.hasValidLicense || hasPickedAGPLv3) {
nav.onNext();
nav.onNext(); // Skip license step
} else {
nav.onNext();
}
}}
onSkip={() => {
// After skipping, go to license step or apps step
if (props.hasValidLicense || hasPickedAGPLv3) {
nav.onNext();
nav.onNext(); // Skip license step
} else {
nav.onNext();
}
Copy link

Choose a reason for hiding this comment

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

Duplicated Navigation Logic

The navigation logic within the onSubmit and onSkip handlers for the AdminEmailConsent component is identical. This duplication violates the DRY (Don't Repeat Yourself) principle. If the navigation flow after this step needs to change, it will require updates in two separate places, increasing the risk of introducing inconsistencies and making maintenance more difficult.

Standards
  • Clean-Code-DRY

Copy link

@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: 0

🧹 Nitpick comments (5)
apps/web/modules/settings/my-account/components/AdminEmailConsentBanner.tsx (1)

13-15: Decouple banner from hard‑coded setup step index

The /auth/setup?step=2 target assumes AdminEmailConsent will always be step 2 of the wizard. That’s true today for self‑hosted setups, but it’s easy to break if steps are reordered again.

Consider centralizing the “email consent step index” in setup-view (e.g. an exported constant or helper) or using a more semantic query flag that setup-view translates into the right step, to avoid future coupling.

Also applies to: 25-31

apps/web/public/static/locales/en/common.json (1)

770-776: Tighten wording of new locale strings

The keys are consistent and descriptive, but you may want to tweak phrasing slightly:

  • self_hosted_admin_email_description: “To stay informed about upcoming updates, vulnerabilities**,** and more, we highly recommend…” reads a bit smoother, and maybe split into two sentences for readability.
  • admin_email_label: “Admin email address” or “Administrator email” is more natural than “Email of admin”.

These are minor UX polish changes; behavior is fine as-is.

apps/web/components/setup/AdminEmailConsent.tsx (1)

32-82: Clarify consent submission behavior and normalize email

A few small improvements to consider here:

  • Normalize email before sending: Right now you validate the raw string and send it as-is. To match the admin user flow and avoid subtle duplicates, consider trimming and lowercasing before appending, e.g. const email = values.email.trim().toLowerCase(); and validate against that.
  • Multiple consent params: When both checkboxes are checked you end up with ...?consent=product&consent=marketing. If the headless router expects repeated params, this is fine; if not, you may want to encode as consent=product,marketing or consents[]=.... Worth double‑checking the backend expectation.
  • Proceeding on network failure: Because onSubmit() is called in finally, the wizard will advance even if the fetch fails (network error, DNS, etc). If “best effort, never block setup” is the product requirement, this is ideal; otherwise you might only call onSubmit() on success and surface an error state instead.

If you decide to adjust any of the above, you can keep the rest of the form logic and UI exactly as-is.

Also applies to: 95-148

apps/web/modules/auth/setup-view.tsx (1)

34-47: Align step constants/IDs with the new AdminEmailConsent step

The new self‑hosted flow looks correct, but there are a couple of maintainability nits:

  • SETUP_VIEW_SETPS.LICENSE is still 2, yet for self‑hosters the second step is now AdminEmailConsent. Defaulting to step 2 when userCount > 0 effectively lands on the consent step, not license. That’s intended behavior now, but the constant name is misleading.
  • Both AdminEmailConsent and LicenseSelection use "wizard-step-2" as id/name in some combinations, and for self‑hosters the license step is no longer the second step. This is mostly cosmetic, but unique, position‑accurate IDs make debugging and test automation easier.
  • The “skip license” navigation logic (nav.onNext() twice) is duplicated in AdminUser’s onSuccess and AdminEmailConsent’s onSubmit/onSkip. A small helper like goToPostLicenseOrApps(nav, props.hasValidLicense, hasPickedAGPLv3) would centralize the logic and reduce chances of these branches drifting.

Functionally everything should work, but tightening these points would make the wizard flow easier to reason about for future changes.

Also applies to: 48-81, 83-119

apps/web/modules/settings/my-account/profile-view.tsx (1)

267-270: Self-hosted admin gating is correct; consider optional attribute-based gating later

isSelfHostedAdmin correctly limits the banner to non‑cal.com deployments and users with the ADMIN role, and the conditional render block is straightforward and safe with the session optional chaining.

If you later store an “email consent completed” flag in user attributes, this would be a good place to additionally gate the banner on that signal so it only appears when consent is missing (or switch to a subtler entry point once consent has been recorded). For now, the always-on admin banner behavior looks reasonable.

Also applies to: 328-332

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f350542 and 9657dde.

📒 Files selected for processing (6)
  • apps/web/components/setup/AdminEmailConsent.tsx (1 hunks)
  • apps/web/components/setup/AdminUser.tsx (3 hunks)
  • apps/web/modules/auth/setup-view.tsx (4 hunks)
  • apps/web/modules/settings/my-account/components/AdminEmailConsentBanner.tsx (1 hunks)
  • apps/web/modules/settings/my-account/profile-view.tsx (10 hunks)
  • apps/web/public/static/locales/en/common.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
apps/web/modules/settings/my-account/components/AdminEmailConsentBanner.tsx (2)
packages/ui/components/test-setup.tsx (1)
  • useRouter (44-52)
packages/ui/components/button/Button.tsx (1)
  • Button (221-351)
apps/web/components/setup/AdminEmailConsent.tsx (1)
packages/ui/components/button/Button.tsx (1)
  • Button (221-351)
apps/web/modules/settings/my-account/profile-view.tsx (2)
packages/lib/constants.ts (1)
  • IS_CALCOM (43-49)
apps/web/modules/settings/my-account/components/AdminEmailConsentBanner.tsx (1)
  • AdminEmailConsentBanner (9-37)
apps/web/modules/auth/setup-view.tsx (1)
packages/lib/constants.ts (1)
  • IS_CALCOM (43-49)
🔇 Additional comments (3)
apps/web/components/setup/AdminUser.tsx (1)

16-27: onSuccess email propagation is wired correctly

The updated onSuccess?(email?: string) signature plus:

  • AdminUser passing data.email_address.toLowerCase() and
  • AdminUserContainer safely calling onSuccess?.() without args

gives setup-view access to the email when available without breaking the existing “admin already created” path. This looks consistent and type-safe.

Also applies to: 38-43, 78-100

apps/web/modules/settings/my-account/profile-view.tsx (2)

19-21: Imports and underscore-prefixed props look consistent and intentional

Using IS_CALCOM, UserPermissionRole, and AdminEmailConsentBanner here is consistent with the surrounding architecture, and the new _isFallbackImg, _userOrganization, and _usersAttributesPending names follow the common underscore convention for intentionally unused values while keeping the typings and API surface intact. No changes needed.

Also applies to: 25-26, 50-52, 276-283, 506-530


618-622: usersAttributes query aliasing is safe and non-breaking

Renaming isPending to _usersAttributesPending preserves the query behavior while clearly indicating the pending flag is intentionally unused right now, which should keep linting happy without altering runtime behavior.

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

Labels

size:L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants