Skip to content

feat(admin): list pending invites in user details side panel - #1847

Open
Shreyag02 wants to merge 5 commits into
mainfrom
feat/user-details-pending-invites
Open

feat(admin): list pending invites in user details side panel#1847
Shreyag02 wants to merge 5 commits into
mainfrom
feat/user-details-pending-invites

Conversation

@Shreyag02

@Shreyag02 Shreyag02 commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Summary

The user details side panel now lists the organizations a user has been invited to but hasn't joined yet, sitting below their existing memberships. Data comes from FrontierService/ListUserInvitations.

Changes

  • New Invitations section in the user details side panel — one block per invite, showing the org, the role offered, a Status (Pending / Expired), and a relative expiry (in 7 days, flipping to 5 days ago once lapsed).
  • New useOrganizationRoles hook (admin/hooks/) that fetches the platform's default org roles plus the org's custom roles, returning both a roles list and an id → title map.
  • membership-dropdown.tsx now uses that hook (+2 / −40). It previously had its own copy of the same two queries, so both places resolve role titles identically from here on.
  • Side panel header colourList.Header renders its text in a tertiary-coloured inner span; overridden to --rs-color-foreground-base-primary to match design, applied to all three sections.

Technical Details

  • ListUserInvitations is keyed by the invitee's email, not the user uuid — an invitation can exist before the user does. Worth knowing because passing the uuid returns 200 with an empty list rather than an error, so getting it wrong fails silently. The query uses user?.email and is gated on it.
  • The API returns expired invitations too. There's no server-side expires_at filter; rows live until the cleanup cron sweeps them. So Pending / Expired is derived client-side from expires_at vs. now.
  • Expiry text comes from dayjs relativeTime with the stock en locale, so it reads in 7 days / 5 days ago. The new formatInviteExpiry helper in admin/utils/connect-timestamp.ts returns the text and the isExpired flag together, and that module is where dayjs.extend(relativeTime) is installed (a one-time global extend, so any other dayjs consumer in the bundle gains fromNow too). An earlier revision registered a custom en-invite locale to get 7 days left phrasing; that's been dropped — the wording gain wasn't worth a locale registration living in a shared util, and stock output already distinguishes the two directions.
  • An invitation carries only org_id and role_ids, so the org title/avatar comes from the existing useOrganizationLookup and role titles from the new hook. react-query caches both per key, so multiple invite blocks share one fetch.
  • Invitations and memberships have independent loading/error branches — a failure in one won't blank out the other section.
  • Role fetch errors now log once from a useEffect inside the hook. The dropdown previously logged them from the render body, which repeated on every render.

Test Plan

  • Manual testing completed
  • Build and type checking passes

Verified:

  • tsc --noEmit: 21 errors, all pre-existing and in untouched files (organizations/details/edit/billing.tsx, client/utils, tsup.config.ts, …); 0 in the files this PR touches.

  • eslint clean on all touched files; pnpm build succeeds in web/sdk.

  • Expiry rendering checked across the boundaries:

    Case Renders Status
    7-day invite, created today in 7 days Pending
    1 day remaining in a day Pending
    3 hours remaining in 3 hours Pending
    30 minutes remaining in 30 minutes Pending
    30 minutes lapsed 30 minutes ago Expired
    lapsed 5 days ago 5 days ago Expired

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

Not applicable — frontend only, no Go or query-builder changes.

  panel and extract org role fetching into useOrganizationRoles
@vercel

vercel Bot commented Aug 4, 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 Aug 10, 2026 7:34pm

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Added invitation details to the user details side panel, including organization, role, status, and expiration information.
    • Added loading placeholders and error handling while invitation details are retrieved.
    • Consolidated platform and organization role loading for consistent role names across the admin interface.
    • Added clear expired-invitation messaging and relative expiration times.
  • Style
    • Improved user details section header styling for better visual consistency.

Walkthrough

This PR centralizes organization role retrieval for membership editing. It adds invitation querying and invitation detail rendering to the user details side panel, including organization data, role titles, status, expiry text, and related styling.

Changes

Organization roles and invitation panel

Layer / File(s) Summary
Shared organization role retrieval
web/sdk/admin/hooks/useOrganizationRoles.ts
Adds a hook that fetches platform and organization roles, merges the results, builds a role-title lookup, and exposes loading and error state.
Membership role integration
web/sdk/admin/views/users/details/layout/membership-dropdown.tsx
Replaces direct role queries and manual state aggregation with useOrganizationRoles.
Invitation query wiring
web/sdk/admin/views/users/details/layout/side-panel.tsx
Queries invitations by user email, normalizes the response, and renders invitation sections with loading and error states.
Invitation detail rendering and expiry formatting
web/sdk/admin/utils/connect-timestamp.ts, web/sdk/admin/views/users/details/layout/side-panel-invitation.tsx, web/sdk/admin/views/users/details/layout/side-panel-details.tsx, web/sdk/admin/views/users/details/layout/side-panel-membership.tsx, web/sdk/admin/views/users/details/layout/side-panel.module.css
Adds relative expiry formatting and renders invitation organization, roles, status, avatar, expiry details, and list-header styling.

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

Possibly related PRs

  • raystack/frontier#1664: Shares admin role-management code and organization member role assignment flows.
  • raystack/frontier#1862: Overlaps in the role hook, timestamp formatter, membership dropdown, and user-details invitation components.

Suggested reviewers: 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.

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


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 49158fa7-268d-4f6e-adf1-52556c80c8a0

📥 Commits

Reviewing files that changed from the base of the PR and between ad5e9a5 and 9b8c987.

📒 Files selected for processing (4)
  • web/sdk/admin/hooks/useOrganizationRoles.ts
  • web/sdk/admin/views/users/details/layout/membership-dropdown.tsx
  • web/sdk/admin/views/users/details/layout/side-panel-invitation.tsx
  • web/sdk/admin/views/users/details/layout/side-panel.tsx

Comment thread web/sdk/admin/views/users/details/layout/side-panel-invitation.tsx Outdated
Comment on lines +33 to +51
const isExpired = !expires.isAfter(now);
const [from, to] = isExpired ? [expires, now] : [now, expires];
const suffix = isExpired ? "ago" : "left";

const days = to.diff(from, "day");
if (days >= 1) {
return { text: `${days} day${days === 1 ? "" : "s"} ${suffix}`, isExpired };
}

const hours = to.diff(from, "hour");
if (hours >= 1) {
return {
text: `${hours} hour${hours === 1 ? "" : "s"} ${suffix}`,
isExpired,
};
}

return { text: `Less than an hour ${suffix}`, isExpired };
}

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.

We can use dayjs relativeTime plugin
https://day.js.org/docs/en/plugin/relative-time

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

updated and also dropped custom invite expiry locale

@coveralls

coveralls commented Aug 6, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 31424705329

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 increased (+0.1%) to 48.097%

Details

  • Coverage increased (+0.1%) from the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • 267 coverage regressions across 6 files.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

267 previously-covered lines in 6 files lost coverage.

File Lines Losing Coverage Coverage
pkg/server/connect_interceptors/authorization.go 118 0.0%
internal/api/v1beta1connect/group.go 63 78.53%
core/resource/service.go 35 78.99%
internal/api/v1beta1connect/invitations.go 34 72.22%
internal/api/v1beta1connect/domain.go 10 91.67%
internal/api/v1beta1connect/authorize.go 7 34.44%

Coverage Stats

Coverage Status
Relevant Lines: 39919
Covered Lines: 19200
Line Coverage: 48.1%
Coverage Strength: 15.37 hits per line

💛 - Coveralls

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

Caution

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

⚠️ Outside diff range comments (1)
web/sdk/admin/utils/connect-timestamp.ts (1)

38-49: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Refresh invitation expiry state while the panel is open.

formatInviteExpiry calculates fromNow() and isExpired only when SidePanelInvitation renders. The shown consumer has no time-based refresh. If an invitation expires while the panel stays open, the UI can continue to show Pending and stale relative text until another render.

Add a timer at the expiry boundary or refresh this view periodically. Add a test that advances the clock across expiresAt.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7e669235-e24d-41b5-8bf6-dc5d82b8b5c8

📥 Commits

Reviewing files that changed from the base of the PR and between 54da7cf and 33cdb5e.

📒 Files selected for processing (5)
  • web/sdk/admin/utils/connect-timestamp.ts
  • web/sdk/admin/views/users/details/layout/side-panel-details.tsx
  • web/sdk/admin/views/users/details/layout/side-panel-invitation.tsx
  • web/sdk/admin/views/users/details/layout/side-panel-membership.tsx
  • web/sdk/admin/views/users/details/layout/side-panel.module.css
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/sdk/admin/views/users/details/layout/side-panel-invitation.tsx

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