Skip to content

fix(admin): add skeleton loader to invite-user Org select#1735

Open
Shreyag02 wants to merge 2 commits into
mainfrom
fix/invite-user-org-select-missing-skeleton
Open

fix(admin): add skeleton loader to invite-user Org select#1735
Shreyag02 wants to merge 2 commits into
mainfrom
fix/invite-user-org-select-missing-skeleton

Conversation

@Shreyag02

@Shreyag02 Shreyag02 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Summary

The Organization select in the Users-list "Invite user" dialog did not render a skeleton loader while its data was loading. This PR adds the skeleton (driven by the real query loading state) and applies a set of consistency cleanups across the admin invite dialogs and the client SDK invite dialog.

Following the review discussion, the previous "disable the submit button" pattern has been replaced with schema-level validation and visible field-level error messages. The submit button no longer silently disables; instead, missing required fields and malformed emails surface inline messages through the Field component.

Changes

  • Add a skeleton loader to the Org select (and the Role/Email fields) in the Users-list invite dialog, gated on the real query loading state rather than a hardcoded flag.
  • Replace the disabled-submit pattern with strict schema validation and visible field-level error messages.
  • Migrate the invite dialogs' form groups to the apsara Field composite (label + error + aria-invalid), replacing the custom Label + error Text markup.
  • Always render the Emails Field (skeleton-swapping only the inner control) so its label no longer pops in after loading, consistent with the Role/Org fields.
  • Remove the now-unused invite-users-dialog-label and form-error-message CSS classes from invite-users.module.css.
  • Apply the same Field migration and validation approach to the org-Members "Invite user" dialog and the client SDK invite-member-dialog for consistency.

Technical Details

  • Replaced a hardcoded const isLoading = true (which left the dialog stuck on skeletons) with isLoading = isRolesLoading || isOrganizationsLoading.
  • Validation is now enforced by the form schema:
    • Admin dialogs (zod): role and organizationId are required; emails is required and each address is validated.
    • Client SDK (yup): type (role) is required; emails is required with a .test that validates every comma-separated address.
  • Errors render inline via Field; the submit button is no longer gated on form values. disabled is retained only while the roles/orgs queries are loading (when the fields are skeletons), so it never blocks a completed form.
  • The Members dialog intentionally has no skeleton — its roles are provided synchronously by OrganizationContext, so there is no loading state to gate on.
  • The shared invite-users-dialog-label and form-error-message CSS classes are retained in the Members dialog's stylesheet because add-tokens-dialog.tsx still uses them.

Test Plan

  • Manual testing completed
  • Build and type checking passes

SQL Safety (if your PR touches *_repository.go or goqu.*)

N/A

@Shreyag02 Shreyag02 requested a review from rohanchkrabrty July 3, 2026 10:19
@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Jul 6, 2026 1:14pm

@Shreyag02 Shreyag02 requested a review from rohilsurana July 3, 2026 10:19
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes
    • Improved invite-user and invite-member forms with stricter validation for required fields and comma-separated email lists.
    • Submit is now disabled based on derived form validity and loading state, preventing premature/incomplete invites.
  • UI Improvements
    • Updated invite dialogs to use consistent form field rendering (including centralized error display).
    • Loading sections now show skeleton placeholders for smoother, clearer progress feedback.

Walkthrough

Two invite-user views now use Field-based form rendering with stricter email and required-field validation. The users list form also adds shared loading-state handling with Skeleton components, and unused CSS rules were removed.

Changes

Invite users form UX refactor

Layer / File(s) Summary
Organization invite dialog validation and fields
web/sdk/admin/views/organizations/details/layout/invite-users-dialog.tsx
Tightens emails and role validation, replaces manual label/error layout with Field wrappers, and derives submit gating from current form state.
Users list validation and loading state
web/sdk/admin/views/users/list/invite-users.tsx
Tightens schema validation, introduces a combined loading flag, and switches imports to apsara Field and Skeleton.
Users list field rendering and submit state
web/sdk/admin/views/users/list/invite-users.tsx, web/sdk/admin/views/users/list/invite-users.module.css
Refactors email, role, and organization controls into Field wrappers with skeleton gating, disables submit while loading, and removes unused label/error CSS rules.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

Suggested reviewers: rsbh, paanSinghCoder, rohanchkrabrty

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
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.

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
web/sdk/admin/views/organizations/details/layout/invite-users-dialog.tsx (1)

111-129: 📐 Maintainability & Code Quality | 🔵 Trivial

Duplicate email-parsing / disable logic across both invite dialogs.

This isDisabled computation (parse emails, check role, submitting, errors) is duplicated almost verbatim in web/sdk/admin/views/users/list/invite-users.tsx. Consider extracting a shared hook (e.g. useInviteFormDisabled(values, isSubmitting, errors)) to avoid divergence when validation rules change.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1b195017-2b13-4e58-a43b-dbe15e9b8041

📥 Commits

Reviewing files that changed from the base of the PR and between 854c122 and 99ce08a.

📒 Files selected for processing (3)
  • web/sdk/admin/views/organizations/details/layout/invite-users-dialog.tsx
  • web/sdk/admin/views/users/list/invite-users.module.css
  • web/sdk/admin/views/users/list/invite-users.tsx
💤 Files with no reviewable changes (1)
  • web/sdk/admin/views/users/list/invite-users.module.css

Comment thread web/sdk/admin/views/users/list/invite-users.tsx Outdated
@coveralls

coveralls commented Jul 3, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 28794203855

Warning

Build has drifted: This PR's base is out of sync with its target branch, so coverage data may include unrelated changes.
Quick fix: rebase this PR. Learn more →

Coverage remained the same at 44.865%

Details

  • Coverage remained the same as the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 37608
Covered Lines: 16873
Line Coverage: 44.87%
Coverage Strength: 12.49 hits per line

💛 - Coveralls

Comment thread web/sdk/admin/views/organizations/details/layout/invite-users-dialog.tsx Outdated
Comment thread web/sdk/admin/views/users/list/invite-users.tsx Outdated
Comment thread web/sdk/admin/views/users/list/invite-users.tsx

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
web/sdk/client/views/members/components/invite-member-dialog.tsx (1)

34-46: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Minor: schema re-instantiated per validation call.

yup.string().email() is created fresh on every email in the list during .test; hoisting it outside the test callback avoids redundant object construction on each keystroke/submit.

♻️ Suggested tweak
+const emailValidator = yup.string().email();
+
 const inviteSchema = yup.object({
   ...
     .test('emails', 'Enter valid email address(es)', value => {
       ...
       return (
         emailList.length > 0 &&
-        emailList.every(email => yup.string().email().isValidSync(email))
+        emailList.every(email => emailValidator.isValidSync(email))
       );
     })
 });

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3802b7be-3d7f-4f7d-9169-c71410692f5c

📥 Commits

Reviewing files that changed from the base of the PR and between 99ce08a and 21235e4.

📒 Files selected for processing (3)
  • web/sdk/admin/views/organizations/details/layout/invite-users-dialog.tsx
  • web/sdk/admin/views/users/list/invite-users.tsx
  • web/sdk/client/views/members/components/invite-member-dialog.tsx

Comment on lines +163 to +166
<Field
label="Emails"
error={errors?.emails?.message || errors?.emails?.[0]?.message}>
{isLoading ? (

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Emails error message only surfaces for the first invalid entry.

errors?.emails?.[0]?.message only catches a validation failure at array index 0. Since the schema validates each comma-separated email independently (z.array(z.string().email())), an invalid email at any later position (e.g. "valid@example.com, bad-email") sets the error at errors.emails[1], which this fallback never reads. Submission stays blocked but the user sees no error message — contrary to the "visible error messages" approach agreed on in prior review discussion.

🐛 Suggested fix
-              <Field
-                label="Emails"
-                error={errors?.emails?.message || errors?.emails?.[0]?.message}>
+              <Field
+                label="Emails"
+                error={
+                  errors?.emails?.message ||
+                  (Array.isArray(errors?.emails)
+                    ? errors.emails.find(e => e?.message)?.message
+                    : undefined)
+                }>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Field
label="Emails"
error={errors?.emails?.message || errors?.emails?.[0]?.message}>
{isLoading ? (
<Field
label="Emails"
error={
errors?.emails?.message ||
(Array.isArray(errors?.emails)
? errors.emails.find(e => e?.message)?.message
: undefined)
}>
{isLoading ? (

variant="solid"
color="accent"
disabled={isDisabled}
disabled={isLoading}

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Submit button no longer gates on required fields/email validity.

disabled={isLoading} only accounts for loading state; the prior isDisabled check (email list non-empty, type selected, not submitting) was removed and nothing replaces it. This conflicts with the PR's stated goal of applying the same submit-gating behavior to this dialog — the button is now clickable with empty/invalid fields, even though the yup resolver will still block the actual submission on click.

🔧 Suggested fix
   const {
     register,
     control,
     handleSubmit,
     reset,
-    formState: { errors, isSubmitting }
+    formState: { errors, isSubmitting, isValid }
   } = useForm({
-    resolver: yupResolver(inviteSchema)
+    resolver: yupResolver(inviteSchema),
+    mode: 'onChange'
   });
-                disabled={isLoading}
+                disabled={isLoading || isSubmitting || !isValid}

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants