Expired invitations now have a visible home without changing pending counts
The Team page now fetches Clerk expired organization invitations and gives them a dedicated tab between Pending and Deactivated. Pending invitations still drive the Pending badge and seat usage; expired rows are display-only in this story.
Problem
Before this story, listTeamInvitations() asked Clerk only
for pending and accepted. Once an invitation
lapsed, Clerk moved it to expired and the firm admin lost
visibility on the Team page.
What changed
| Area | Change | Why |
|---|---|---|
src/lib/team-invitations/server.ts |
Fetches Clerk pending, accepted, and expired invitations. |
Clerk is the source of truth for expiry; the app should display that state instead of recomputing it. |
presentation.ts |
Adds an expired tab model, row filter, and count. |
Expired rows stay separate from live pending invitations and deactivated members. |
TeamListToolbar |
Extends the existing SegmentedFilter order to Members · Pending · Expired · Deactivated. |
Uses the established list toolbar primitive and avoids a bespoke filter control. |
PendingInvitesTable / PendingInviteRow |
Reuses the existing invitation table and row names; expired rows hide actions. | The row already knows how to render expired status, but Story-1750 is display-only and must not expose resend. |
User-facing tab order
The Expired tab renders the existing invitation row shape: invitee identity, capability, Expired status, and invited date. It does not render the resend/revoke action menu.
Count boundary
| Metric | Includes expired? | Evidence |
|---|---|---|
| Pending tab count | No | teamRowsForTab("pending") filters to status === "pending". |
| Expired tab count | Yes, separately | teamRowsForTab("expired") filters to status === "expired". |
| Seat usage | No | seats.active + pendingInvitations.length is unchanged. |
Server totalCount | No | The server response still computes pending-only totalCount for Bug-1354. |
Acceptance evidence
- Done Expired invitations appear only in the new Expired tab.
- Done Pending tab, Pending count, and seat usage stay pending-only.
- Done Expired rows render identity, role, status badge, and invited date with no action menu.
- Done Team page, content, and toolbar Storybook states include expired invitations.
- Done New tab/search/error/empty copy is present in both English and Hindi messages.
Verification
pnpm lint- passed with one unrelated existingno-img-elementwarning insrc/components/add-matter/avatar.tsx.pnpm test- 124 files passed, 799 tests passed.pnpm build- passed.pnpm storybook:build- passed.git diff --check- passed.
Files changed
18 file(s), +310 / -28 lines
| Path | Role | + | - |
|---|---|---|---|
| messages/en.json | Config | +6 | -0 |
| messages/hi.json | Config | +6 | -0 |
| src/components/settings/team/pending-invite-row.tsx | Component | +1 | -1 |
| src/components/settings/team/pending-invites-table.tsx | Component | +3 | -1 |
| src/components/settings/team/presentation.test.ts | Component | +42 | -0 |
| src/components/settings/team/presentation.ts | Component | +10 | -2 |
| src/components/settings/team/team-list-content.stories.tsx | Component | +47 | -7 |
| src/components/settings/team/team-list-content.tsx | Component | +5 | -1 |
| src/components/settings/team/team-list-page.stories.tsx | Component | +7 | -0 |
| src/components/settings/team/team-list-page.test.tsx | Component | +142 | -0 |
| src/components/settings/team/team-list-page.tsx | Component | +4 | -4 |
| src/components/settings/team/team-list-toolbar.stories.tsx | Component | +6 | -1 |
| src/components/settings/team/team-list-toolbar.tsx | Component | +6 | -0 |
| src/components/settings/team/team-storybook-helpers.tsx | Component | +3 | -1 |
| src/components/settings/team/use-team-tab-state.ts | Component | +2 | -0 |
| src/lib/team-invitations/server.test.ts | Library | +4 | -3 |
| src/lib/team-invitations/server.ts | Library | +9 | -7 |
| tests/unit/team-presentation.test.ts | Test | +7 | -0 |