Skip to content

fix(committees): block save when org name typed but not resolved - #1124

Merged
andrest50 merged 18 commits into
mainfrom
fix/LFXV2-2737
Jul 22, 2026
Merged

fix(committees): block save when org name typed but not resolved#1124
andrest50 merged 18 commits into
mainfrom
fix/LFXV2-2737

Conversation

@andrest50

@andrest50 andrest50 commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds an org validation gate to the Add Member dialog: when the organization field is visible, typing a free-form name that is never resolved through the CDP org search (no `organization_id` and no valid HTTPS URL) now blocks submission and shows a red error message under the search field
  • Reactive-as-you-type: the error appears immediately when the user has typed a search term with no confirmed selection — same UX as the email-invalid warning (no submit click needed). After the first submit attempt, the error also surfaces for a name-with-no-id-or-url state (e.g. autofill that only wrote the org name)
  • Mirrors the accept-flow root cause (LFXV2-2737): an unresolved org name leaves `organization_id` null, which previously allowed creating an invite with an unverifiable org — blocking the invitee silently on accept

Ticket

LFXV2-2737

Test plan

  • Open Add Member dialog on a committee that requires organization
  • Type an org name that does not appear in the search results (do not select from dropdown) — red error appears immediately under the org field: "Organization not found. Select from the search results, or clear the field to leave it blank."
  • Select a valid org from search results — error clears, submission succeeds
  • Select org A, then re-type to search for org B without selecting — error appears while typing
  • Click "I want to create 'Org B'" (manual mode) — website field appears, Send Invites is disabled until a valid https:// URL is entered
  • In manual mode: enter valid URL, Send Invites enables
  • Click "← Back to search" — form resets to empty, Send Invites returns to email-gated state
  • Leave org field empty — no error (field is optional)
  • Committees that do not require org: org field hidden, no validation runs

When the organization field is shown on the Add Member dialog, typing a
free-form name that is never resolved through the CDP org search left
organization_id null. The invite would be created with an unverifiable
org name, mirroring the accept-flow bug (LFXV2-2737) where a mismatched
pre-populated name silently blocks the Join Group button.

Gate onSubmit() behind an orgInvalid() check: if a name is entered but
no org ID has been resolved, set orgSubmitAttempted and return early.
Show a red validation message under the org search field explaining that
the user must select from search results.

LFXV2-2737

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Move orgFormValues (toSignal), orgInvalid (computed), and showOrgError
(computed) from inline class body to private init functions, matching
the initSearchResults() pattern per component-organization convention.

LFXV2-2737

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings July 17, 2026 17:46
@andrest50
andrest50 requested a review from a team as a code owner July 17, 2026 17:46
@cursor

cursor Bot commented Jul 17, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Scoped UI and form validation in the committee invite dialog; no auth, API, or persistence changes beyond preventing invalid org payloads.

Overview
Add Member now validates the optional organization field when the committee requires org on accept: invites cannot be sent if the user typed a name that was never resolved (no organization_id and no valid HTTPS URL), matching the accept-flow issue in LFXV2-2737.

Validation is reactive—an “Organization not found…” alert appears as soon as there is search text without a confirmed selection, and Send Invites stays disabled until the org is valid or the field is cleared. Manual “create org” mode relies on required HTTPS website validators on organization_url (with inline errors); switching back to search clears stale name/URL/id and restores prior validators.

lfx-organization-search gains idControl (clears stale ids in manual mode), toggles domain validators in manual mode, and keeps searchTerm in sync with the parent name control for pending-search detection. Employer autofill from people search now sets organization_id and organization_url without immediately clearing them.

Reviewed by Cursor Bugbot for commit 506445f. Bugbot is set up for automated code reviews on this repo. Configure here.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

The add-member dialog validates organization selection after submission attempts, preserves organization autofill data, blocks invalid invitations when an organization is required, and displays an organization selection error.

Changes

Organization validation

Layer / File(s) Summary
Organization validation and submit gating
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts
Organization signals derive validity and error visibility from form state and submit attempts; organization changes and user-based autofill update related identifiers and URLs; onSubmit blocks invalid invitations when an organization is required.
Organization error display
apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.html
The template displays an organization selection error with an error icon when showOrgError() is true.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly matches the main change: preventing save when an organization name is entered but not resolved.
Description check ✅ Passed The description is directly related to the PR and accurately describes the validation gate and error behavior.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/LFXV2-2737

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds submit-time validation to prevent committee invitations with unresolved organization names.

Changes:

  • Tracks organization validity and submit attempts.
  • Displays an organization resolution error.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
add-member-dialog.component.ts Adds organization validation gating.
add-member-dialog.component.html Renders the validation error.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Allow name+URL as valid org state (employer autofill path does not
resolve an org_id but provides a known name and website). Patch both
organization and organization_url from the employer autofill so the
new validation is satisfied without requiring a manual re-selection.

Add role="alert" to the validation error paragraph so screen readers
announce the message when it appears after a submit attempt.

LFXV2-2737

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings July 17, 2026 18:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

@github-actions

Copy link
Copy Markdown

🚀 Deployment Status

Your branch has been deployed to: https://ui-pr-1124.dev.v2.cluster.linuxfound.info

Deployment Details:

  • Environment: Development
  • Namespace: ui-pr-1124
  • ArgoCD App: ui-pr-1124

The deployment will be automatically removed when this PR is closed.

The name-change subscription cleared organization_id but left
organization_url intact. A user who selected Org A (getting both
id + url) then typed a new free-form name would have the id cleared
while Org A's URL remained, satisfying the name+URL validity check
and bypassing the org validation gate.

Fix: also clear organization_url in the subscription patch so that
any name deviation from the resolved org invalidates both fields.

To keep the employer-autofill path working, set resolvedOrganizationName
to the employer name before patching so the subscription treats the
autofilled name as the resolved name and does not immediately wipe the
URL that was just written.

Addresses copilot-pull-request-reviewer and cursor[bot] findings on PR #1124.

LFXV2-2737

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings July 17, 2026 18:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

@andrest50

Copy link
Copy Markdown
Contributor Author

Review Feedback Addressed

Commits: 88587ca, fc8c9e3

Changes Made

  • add-member-dialog.component.ts — Added orgSubmitAttempted gate in onSubmit() so the button blocks and shows an inline error when an org name is typed but not CDP-resolved (per copilot[bot])
  • add-member-dialog.component.ts — Extracted orgFormValues, orgInvalid, and showOrgError into private init*() functions per the component-organization convention (per copilot[bot])
  • add-member-dialog.component.ts — Constructor subscription now clears both organization_id and organization_url when the name field changes away from the last resolved name, closing the stale-URL bypass (per copilot[bot], cursor[bot])
  • add-member-dialog.component.ts — Autofill sets resolvedOrganizationName before patching the form so the subscription doesn't immediately wipe the URL that was just written (paired fix for the stale-URL commit)
  • add-member-dialog.component.html — Added role="alert" and aria-hidden="true" on the exclamation icon for the org error message (per copilot[bot])

Questions Answered

  • Thread 3 (cursor[bot]) — autocomplete skips validation: The lfx-organization-search component maintains its own internal form; typing does not update the parent form. The parent's organization, organization_url, and organization_id fields are written only on an explicit CDP resolution (dropdown selection or resolveCurrentEntry() at submit). This gap predates this PR and is out of scope; left unresolved for a follow-up if the team decides to tighten it.

Threads Resolved

4 of 5 unresolved threads addressed in this iteration.

Still Open

  • Thread 3 (cursor[bot]) — Pre-existing behavior in lfx-organization-search; no code change in this PR. Left unresolved for a potential follow-up.

The autofill path called getUserCurrentEmployer() which returns
CommitteeOrganizationReference via currentEmployerFromWorkExperiences().
That util returns { name, id } only — no website. The patchValue call
only set organization and organization_url (always ''), leaving
organization_id null. orgInvalid() then saw hasOrgId=false, hasUrl=false
and blocked every autofilled submit.

Fix: include organization_id: employer.id ?? null in the patchValue so
the autofill carries the CDP ID that is already available, satisfying the
validation without requiring a URL.

Per copilot-pull-request-reviewer.

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings July 17, 2026 18:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

- Clear resolvedOrganizationName when the name-change subscription
  invalidates the form, so returning to a previously resolved name
  is treated as unresolved and cannot bypass the URL-based org check
- Remove emitEvent: false from patchValue so orgFormValues signal
  updates immediately and orgInvalid() reads current state

LFXV2-2737

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings July 17, 2026 19:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8d32cb4. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (3)

apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:330

  • The pending autocomplete text is only considered when the parent name is empty. After selecting Org A and then typing an unresolved Org B, keystrokes update only the child searchTerm; the parent still contains A's name/ID/URL, so this guard passes and resolveCurrentEntry() submits Org A while the field displays B. Treat any mismatch between the displayed search text and the parent name as unresolved.
      const pendingSearch = this.organizationSearch()?.searchTerm() ?? '';
      if (!hasName && pendingSearch) return true;

apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:148

  • Returning unconditionally in manual mode preserves resolution data from the previous selection. If Org A is selected, the user types Org B and chooses “I want to create,” the name changes to B while A's ID and website remain; the website is already valid, so B can be submitted with A's metadata. Clear the prior resolution once when manual mode diverges from the resolved name, while preserving later edits to the new manual entry.
        if (this.organizationSearch()?.manualMode()) return;

apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts:187

  • Validators.required is the only validator here that rejects null, but the manual-mode template renders messages only for trimmedRequired and httpsUrl. Since clearing the search sets the domain control to null, typing another name and entering manual mode can produce a touched, invalid control with only a required error and no explanation. Render the required message for either required or trimmedRequired.
      const validators = this.domainRequired() ? [Validators.required, trimmedRequired(), httpsUrlValidator()] : [httpsUrlValidator()];

- org-search: markAsTouched() on domain control immediately on entering
  manual mode so the required-URL error is visible from the start and
  the disabled Send Invites button is always self-explaining (T1/T2)
- add-member-dialog: replace isValidUrl() with an inline https-protocol
  check (new URL(v).protocol === 'https:') in initOrgInvalid() so search-
  mode and manual-mode validation are consistent; remove unused import (T3)

LFXV2-2737

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings July 22, 2026 18:50
switchToSearchMode() now clears the parent form's organization name and
URL values (matching onSearchClear() behavior) and resets the internal
search input. Previously the stale manual-mode name remained in the form
with no org-id, making orgInvalid() return true and keeping Send Invites
greyed out after switching back to search mode.

LFXV2-2737

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:119

  • canSubmit prevents onSubmit() from ever setting orgSubmitAttempted, but showOrgError() remains false for invalid states where the parent already has a name. This occurs for the supported employer-autofill case when currentEmployerFromWorkExperiences() returns a name with a null ID (packages/shared/src/utils/invitation.utils.ts:24-28) and no website: Send Invites is disabled with no explanation or actionable error. Keep the button enabled when emails are submittable and let the existing submit guard expose the organization error.
  public readonly canSubmit = computed(
    () => !this.submitting() && this.categorized().toInvite.length > 0 && !(this.showOrganizationField() && this.orgInvalid())
  );

apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts:187

  • Validators.required reports { required: true } for null, but the manual-mode template only renders trimmedRequired and httpsUrl errors. After clearing an existing autocomplete selection (which sets the domain to null) and then choosing “I want to create,” the website is invalid and Send Invites is disabled, yet no website error is shown. Render the required error together with trimmedRequired (or use a validator that emits the displayed error key).
      const validators = this.domainRequired() ? [Validators.required, trimmedRequired(), httpsUrlValidator()] : [httpsUrlValidator()];

Copilot AI review requested due to automatic review settings July 22, 2026 18:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (2)

apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:323

  • pendingSearch is only considered when the parent name is empty. After selecting/autofilling Org A, editing the autocomplete to unresolved Org B changes only the child search text; the parent still contains A's name and resolution data, so this returns valid and submission resolves/sends A even though B is displayed. Compare the trimmed child text with the parent organization name, and ensure the error predicate also handles a mismatch.
      const pendingSearch = this.organizationSearch()?.searchTerm() ?? '';
      if (!hasName && pendingSearch) return true;

apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:119

  • Disabling on orgInvalid() can make the new validation gate impossible to trigger. The employer helper can return a name with a null ID and no website; that autofill is invalid, but showOrgError() remains false because the parent has a name, while this disables the only action that sets orgSubmitAttempted. Keep the button available when emails are submittable and let onSubmit() block and reveal the organization error.
  public readonly canSubmit = computed(
    () => !this.submitting() && this.categorized().toInvite.length > 0 && !(this.showOrganizationField() && this.orgInvalid())
  );

- switchToManualMode() now clears organization_url and organization_id
  before applying manual-mode validators, preventing a prior selection's
  URL from silently passing httpsUrlValidator for a different new org
- Template now checks errors['required'] alongside errors['trimmedRequired']
  so the "Website is required" message appears when the control is null
  (Validators.required fires on null, trimmedRequired only on whitespace)

LFXV2-2737

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings July 22, 2026 19:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (2)

apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:324

  • Typing after a prior selection is still accepted as that prior organization. The child search term changes, but the parent retains the selected name/ID/URL; because this check only considers pendingSearch when hasName is false, orgInvalid() stays false and submission sends the stale selected org while the UI displays the newly typed unresolved text. Compare the pending search text with the parent name and reject any non-empty mismatch.
      const pendingSearch = this.organizationSearch()?.searchTerm() ?? '';
      if (!hasName && pendingSearch) return true;
      if (!hasName) return false;

apps/lfx-one/src/app/shared/components/organization-search/organization-search.component.ts:191

  • Setting the website to null reopens the required-website bypass in AcceptInviteOrganizationDialogComponent. Switching to manual mode makes its isNewOrg() effect rerun and replace these validators with trimmedRequired() plus httpsUrlValidator(); both explicitly accept null, so the control becomes valid and a new organization can be confirmed without a website. Clear to an empty string so the parent's trimmed-required validator still rejects it after that effect runs.
      domainCtrl.setValue(null);

@audigregorie audigregorie left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review Summary

Targeted, well-reasoned fix for the LFXV2-2737 silent-acceptance bug — gating on !hasOrgId && !hasValidUrl correctly distinguishes "typed but never resolved" from "selected with a normalized domain but no CDP id." Validator application on manual-mode entry/exit is symmetric and restores the original ValidatorFn.

Two things to resolve before merge:

  1. UX divergence — the Summary states the error only appears after the first submit attempt, but initShowOrgError() surfaces it reactively as the user types. Either gate on orgSubmitAttempted() or update the PR description/test plan. (See inline thread.)
  2. No tests for the new orgInvalid / showOrgError state machine; given this guards a regression-prone accept-flow path, a few component spec cases are warranted.

Major — outside the diff

  • No unit tests for initOrgInvalid() / initShowOrgError() branching (manual vs search mode, hasName/hasOrgId/hasValidUrl matrix, autofill vs typed, pendingSearch). The repo has specs for comparable components; this behavioral logic should be covered.
  • Redundant organization_id patch on submit — in onSubmit, resolveCurrentEntry() already emits onOrganizationResolvedonOrgResolved()patchValue({ organization_id }), then the next callback patches organization_id again from the same result.id. Harmless, but a future reader may wonder which path wins. Consider dropping organization_id from the next patch and keeping only organization: result.name.

What's done well

  • Correct root-cause gating: !hasOrgId && !hasValidUrl allows a selected suggestion (normalized domain) through even when CDP resolve returns no id, while blocking pure free-text-no-resolution.
  • Autofill path sets resolvedOrganizationName before patching so the name-change subscription doesn't immediately wipe organization_id/organization_url.
  • Symmetric validator save/restore on manual-mode toggle; role="alert" + data-testid on the error message.

See the inline threads for the remaining Minor nits (hardcoded organization_id control name, domainOriginalValidator re-entrancy, dual searchTerm writers, initOrgFormValues re-emit cadence).

@audigregorie audigregorie left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Coding-standards pass

Follow-up audit against .claude/rules/ (global comment rules, angular-standards.md, component-organization.md, styling.md, ssr-safety.md). The PR is largely compliant — init*() structure, signal naming, inject() DI, @if/@for templates, lfxColors scale usage, and SSR safety all check out.

Two hard violations (over-long comments) and one design suggestion below. The over-long comments violate the global rule: "Comments must be one or two lines maximum. No exceptions — not even when existing functions in the same file use longer comments."

- Add idControl input to OrganizationSearchComponent; switchToManualMode()
  now clears the id control via input rather than hardcoding 'organization_id'
- Guard domainOriginalValidator capture with undefined check to prevent
  double-entry from overwriting the saved original with manual validators
- Shorten 4-line and 3-line comments to 2-line max per global comment rule

LFXV2-2737

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Copilot AI review requested due to automatic review settings July 22, 2026 21:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:323

  • This still allows a stale selected organization to be submitted when the user edits the autocomplete text. After selecting Org A, typing unresolved Org B only changes searchTerm; the parent still contains A's name/ID/URL, so hasName is true, this branch is skipped, and resolveCurrentEntry() submits Org A even though the field displays B. Compare the pending search text with the parent organization (and make showOrgError() use the same mismatch condition) so edited text invalidates the prior selection.
      const pendingSearch = this.organizationSearch()?.searchTerm() ?? '';
      if (!hasName && pendingSearch) return true;

Copilot AI review requested due to automatic review settings July 22, 2026 22:04
@andrest50
andrest50 merged commit 299682f into main Jul 22, 2026
11 of 12 checks passed
@andrest50
andrest50 deleted the fix/LFXV2-2737 branch July 22, 2026 22:04
@github-actions

Copy link
Copy Markdown

🧹 Deployment Removed

The deployment for PR #1124 has been removed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

apps/lfx-one/src/app/modules/committees/components/add-member-dialog/add-member-dialog.component.ts:323

  • This only treats pending autocomplete text as unresolved when the parent name is empty. After selecting Org A, typing Org B updates the child searchTerm but leaves Org A's parent name/ID/URL intact, so this guard remains valid and submission sends Org A while the UI shows Org B. Treat any non-empty pending text that differs from the parent name as unresolved.
      // User typed in the search box but never selected a result (e.g. org doesn't exist in CDP).
      // The parent form stays empty in that case, so check the component's pending search text.
      const pendingSearch = this.organizationSearch()?.searchTerm() ?? '';
      if (!hasName && pendingSearch) return true;

Comment on lines +348 to +349
const hasName = !!(this.orgFormValues().organization ?? '').trim();
return !hasName && !!(this.organizationSearch()?.searchTerm() ?? '');
andrest50 added a commit that referenced this pull request Jul 23, 2026
Add orgInvalid and showOrgWarning computed signals, mirroring the
pattern from the add-member dialog (PR #1124). The existing isNewOrg()
only inspected the parent form's organization value, which is only set
on explicit selection or manual-mode entry — so typing in the search box
with no confirmed result left the button enabled and the warning hidden.

orgInvalid covers three cases:
  - manual mode: URL validator status (same as before via urlStatus)
  - pending search: searchTerm() is non-empty but organization is empty
  - unresolved name: name in form but no organization_id and URL invalid

showOrgWarning gates the warning text to non-manual-mode only since the
URL field's own inline validation owns display in manual/create mode.

canConfirm now delegates to !orgInvalid() instead of the previous
(!isNewOrg() || urlStatus() === 'VALID') check.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
andrest50 added a commit that referenced this pull request Jul 23, 2026
…1156)

* fix(committees): pre-resolve org before accept-invite dialog opens

When a pre-filled organization has a name but no CDP id (sourced from a
free-text invite or a work-experience employer with no linked account),
isNewOrg() in the accept dialog becomes true, adds a required URL
validator, and onConfirm() silently returns invalid before any network
call can be made.

Fix by resolving the org against CDP in InvitationAcceptFlowService
before opening the dialog. On success the dialog opens with a valid
organization_id, isNewOrg() stays false, and the user can confirm
immediately. On failure the context passes through unchanged so the
existing manual-fallback behaviour is preserved.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(review): guard against empty name in resolved org

Preserve the original org.name if CDP resolveOrganization returns an
empty or missing name, preventing a silent overwrite.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(review): log warning on org pre-resolution failure

Add console.warn in catchError so failed CDP resolve calls surface
in browser devtools instead of silently passing through.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(review): use search-then-resolve to pre-fill org id safely

Replace direct resolveOrganization call (find-or-create, requires
domain) with a two-step read-only approach:
1. searchOrganizations(name) — read-only GET to find the CDP-canonical
   domain without side effects.
2. resolveOrganization only on an exact name match, using the CDP
   domain so the call is a find (not a create).

Adds timeout(2000) so a stalled lookup falls through in 2 s rather
than blocking the dialog indefinitely.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(invite): forward resolved org website to accept dialog context

After a successful CDP search-then-resolve, preResolveOrganization now also
sets organization.website from the CDP-canonical domain via normalizeToUrl.
Without this, buildCommitteeOrganizationPayload (which intentionally drops
organization_id — committee-service rejects CDP UUIDs) would send a name-only
payload and the dialog's organization_url field would open empty.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(invite): show org warning and disable join when unresolved

When the accept dialog opens with a pre-filled org name that has no CDP id
(e.g. pre-resolution failed or org is not in CDP), the Join Group button is
now disabled and a red error message explains that the org must be searched
and selected (or entered manually with a website URL).

Previously the button appeared active but onConfirm() returned silently when
form.valid was false, leaving the user with no feedback.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(review): fix manual org entry and stale resolvedOrganizationName

- canConfirm computed replaces isNewOrg() on the Join button so manual
  mode (name + valid HTTPS URL, no CDP id) can be confirmed; button is
  disabled only while submitting or while the URL is invalid, not whenever
  isNewOrg() is true
- urlStatus toSignal drives canConfirm reactively in zoneless mode
- resolvedOrganizationName initialized from config.data.organization.name
  when the dialog opens with a pre-resolved id, preventing the valueChanges
  handler from clearing organization_id on any programmatic setValue that
  re-sets the same CDP-confirmed name
- Warning copy updated to "I want to create" to match the actual
  org-search button label

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(invite): fix stale urlStatus after validator change in dialog

Replace toSignal(statusChanges) with a WritableSignal for urlStatus.
The effect was calling updateValueAndValidity({ emitEvent: false }),
which suppressed statusChanges — leaving urlStatus stale at VALID even
after validators made the empty URL field invalid. canConfirm() returned
true, the button appeared enabled, and onConfirm() silently bailed on
form.valid — re-creating the original silent-fail for unresolved orgs.

Fix: subscribe to statusChanges via takeUntilDestroyed() to track
user-typed changes, and manually call urlStatus.set(urlControl.status)
inside the effect immediately after updateValueAndValidity so that any
validator-driven status change is always reflected in the signal.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(invite): clear org id when entering manual create mode in dialog

Pass idControl="organization_id" to lfx-organization-search so that
switchToManualMode() clears the pre-resolved organization_id when the
user clicks "I want to create". Without this, clicking "I want to
create" with a pre-resolved org kept organization_id set (because the
typed name equalled resolvedOrganizationName), leaving isNewOrg() false
and bypassing the URL validator — Join Group could be submitted without
a website URL.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(invite): disable join and warn on unresolved org search

Add orgInvalid and showOrgWarning computed signals, mirroring the
pattern from the add-member dialog (PR #1124). The existing isNewOrg()
only inspected the parent form's organization value, which is only set
on explicit selection or manual-mode entry — so typing in the search box
with no confirmed result left the button enabled and the warning hidden.

orgInvalid covers three cases:
  - manual mode: URL validator status (same as before via urlStatus)
  - pending search: searchTerm() is non-empty but organization is empty
  - unresolved name: name in form but no organization_id and URL invalid

showOrgWarning gates the warning text to non-manual-mode only since the
URL field's own inline validation owns display in manual/create mode.

canConfirm now delegates to !orgInvalid() instead of the previous
(!isNewOrg() || urlStatus() === 'VALID') check.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(invite): grey join and show error when returning to search mode

Two fixes for the back-to-search-mode case in the confirm-org dialog:

1. orgInvalid() now returns true for an empty org name even when there
   is no pending searchTerm. Previously the early-return `return false`
   left the button enabled after the org-search component cleared the
   name on switchToSearchMode(), so clicking Join Group silently failed.

2. markAsTouched() is called on organizationControl whenever the name
   is cleared (normalizedName is empty). Because the toSignal subscription
   and our valueChanges subscription both fire synchronously before the
   template re-renders, touched is already true when Angular reads
   organizationControl.touched in the next render cycle — making the
   "Organization is required" error appear without a submit attempt.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(invite): disable Join Group when URL empty in manual mode

`orgInvalid()` delegated to `urlStatus()` for both the manual-mode
and the is-new-org branches. Because `switchToManualMode()` reads the
`domainRequired` input synchronously — before Angular's change-detection
has propagated the updated `isNewOrg()` value — the stale `false` value
caused only `[httpsUrlValidator()]` to be applied. That validator returns
null (valid) for empty strings, so `urlStatus` was set to 'VALID' and the
button stayed enabled with no URL entered.

Add an explicit empty-URL guard before the `urlStatus()` check in both
branches. An empty `organization_url` now immediately returns `true`
(invalid) regardless of validator state, making `canConfirm()` false and
the button disabled the moment manual mode is entered.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

* fix(review): address PR #1156 review feedback

Address review comments from @audigregorie, copilot-pull-request-reviewer:

- accept-invite-organization-dialog.component.ts: add org-name guard to
  manual-mode branch of orgInvalid() so clearing the name while a valid URL
  is set still disables Join Group (per @audigregorie, copilot-pull-request-reviewer)
- accept-invite-organization-dialog.component.ts: remove redundant
  `!hasName && pendingSearch` dead branch — subsumed by `!hasName` (per @audigregorie)
- accept-invite-organization-dialog.component.ts: block submission when
  visible search term has been edited without a new selection, preventing
  stale pre-resolved name/id from being submitted (per copilot-pull-request-reviewer)
- accept-invite-organization-dialog.component.ts: skip resolveCurrentEntry()
  when organization_id is already set to avoid a redundant /resolve POST (per @audigregorie)
- accept-invite-organization-dialog.component.html: replace "Organization not
  found" warning with selection-focused wording accurate while results are
  loading or visible (per copilot-pull-request-reviewer)
- invitation-accept-flow.service.ts: fall back to org.website when
  normalizeToUrl(match.domain) returns null so an original website is not
  silently discarded for empty-domain matches (per @audigregorie)

Resolves 7 review threads. Thread 4 (pre-resolution latency) acknowledged
with a PR description update — addressed separately without a code change.

LFXV2-2751

Generated with [Claude Code](https://claude.ai/code)

Signed-off-by: Andres Tobon <andrest2455@gmail.com>

---------

Signed-off-by: Andres Tobon <andrest2455@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants