fix(admin): Admin org design fixes#1799
Conversation
…single-member orgs
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 WalkthroughSummary by CodeRabbit
WalkthroughOrganization admin tables receive dedicated zero-state views and spacing. Project actions gain conditional member-add controls, improved action buttons, popup styling, and dialog focus handling. Role labels, invoice links, PAT dialogs, and organization navigation presentation are also updated. ChangesOrganization admin UI
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 2✅ Passed checks (2 passed)
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. Comment |
Coverage Report for CI Build 30037257485Coverage remained the same at 46.739%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (8)
web/sdk/admin/views/organizations/details/apis/index.tsx (1)
39-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated
ZeroStatecomponent across org detail views.This
ZeroStateimplementation is structurally identical to the ones added ininvoices/index.tsx(and, per the referenced graph context,members/index.tsx,projects/index.tsx,tokens/index.tsx), differing only inicon/heading/subHeading. Wiring it intoDataTable.ContentviazeroState={<ZeroState />}(Line 208) is correct and consistent with the existing pattern.Consider extracting a shared
<TableZeroState icon heading subHeading />component to avoid maintaining five near-identical copies.Also applies to: 208-208
web/sdk/admin/views/organizations/details/apis/apis.module.css (1)
10-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated zero-state CSS across five module files.
.zero-state-containerand.zero-stateare copy-pasted identically intoinvoices.module.css,members.module.css,projects.module.css, andtokens.module.css. Extract these into a shared CSS module (e.g.,shared/zero-state.module.css) to avoid drift.web/sdk/admin/views/organizations/details/invoices/index.tsx (1)
73-86: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated
ZeroStatecomponent (same pattern as other org detail views).Same structural duplication as
apis/index.tsx'sZeroState. Wiring at Line 197 is correct.Also applies to: 197-197
web/sdk/admin/views/organizations/details/invoices/invoices.module.css (1)
10-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated zero-state CSS.
Same duplication as
apis.module.css; see consolidated comment.web/sdk/admin/views/organizations/details/members/members.module.css (2)
18-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated zero-state CSS.
Same as
apis.module.css; see consolidated comment.
45-55: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated table-action-column reveal pattern.
This hover/focus-within/popup-open reveal block is duplicated near-verbatim in
projects.module.cssandprojects/members/members.module.css. Otherwise the logic is correct —:focus-withinpreserves keyboard accessibility for the hidden actions.web/sdk/admin/views/organizations/details/projects/projects.module.css (1)
18-25: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated zero-state CSS.
Same as
apis.module.css; see consolidated comment.web/sdk/admin/views/organizations/details/tokens/tokens.module.css (1)
10-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicated zero-state CSS.
Same as
apis.module.css; see consolidated comment.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 34c79bf7-d076-4b17-b018-24c0eeb45599
📒 Files selected for processing (21)
web/sdk/admin/utils/helper.tsweb/sdk/admin/views/organizations/details/apis/apis.module.cssweb/sdk/admin/views/organizations/details/apis/index.tsxweb/sdk/admin/views/organizations/details/invoices/columns.tsxweb/sdk/admin/views/organizations/details/invoices/index.tsxweb/sdk/admin/views/organizations/details/invoices/invoices.module.cssweb/sdk/admin/views/organizations/details/members/columns.tsxweb/sdk/admin/views/organizations/details/members/index.tsxweb/sdk/admin/views/organizations/details/members/members.module.cssweb/sdk/admin/views/organizations/details/pat/components/pat-details-dialog.tsxweb/sdk/admin/views/organizations/details/projects/columns.tsxweb/sdk/admin/views/organizations/details/projects/index.tsxweb/sdk/admin/views/organizations/details/projects/members/add-members-dropdown.tsxweb/sdk/admin/views/organizations/details/projects/members/columns.tsxweb/sdk/admin/views/organizations/details/projects/members/index.tsxweb/sdk/admin/views/organizations/details/projects/members/members.module.cssweb/sdk/admin/views/organizations/details/projects/projects.module.cssweb/sdk/admin/views/organizations/details/tokens/index.tsxweb/sdk/admin/views/organizations/details/tokens/tokens.module.cssweb/sdk/admin/views/organizations/list/list.module.cssweb/sdk/admin/views/organizations/list/navbar.tsx
💤 Files with no reviewable changes (1)
- web/sdk/admin/views/organizations/details/pat/components/pat-details-dialog.tsx
| import { DotsHorizontalIcon, UpdateIcon } from "@radix-ui/react-icons"; | ||
| import { DeleteIcon } from "~/admin/assets/icons/DeleteIcon"; | ||
| import { formatTimestamp, TimeStamp } from "~/admin/utils/connect-timestamp"; | ||
| import { formatRoleTitle } from "~/admin/utils/helper"; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Normalize all member-facing role labels.
The table uses formatRoleTitle, but the filter options at Lines 99-101 and assignment actions at Lines 154-167 still render raw role.title. This leaves users seeing inconsistent values such as Owner in the table and Organization Owner elsewhere. Apply the helper to those display labels while preserving the raw title in API payloads.
Also applies to: 47-50
| <IconButton | ||
| size={3} | ||
| onClick={preventClickBubbling} | ||
| data-test-id="admin-project-actions" | ||
| /> | ||
| > | ||
| <DotsHorizontalIcon /> | ||
| </IconButton> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg 'web/sdk/admin/views/organizations/details/projects/(columns|members/columns)\.tsx$|IconButton|DotsHorizontalIcon' || true
echo
echo "Target snippets:"
for f in \
web/sdk/admin/views/organizations/details/projects/columns.tsx \
web/sdk/admin/views/organizations/details/projects/members/columns.tsx
do
if [ -f "$f" ]; then
echo "--- $f"
sed -n '160,215p' "$f"
echo "--- $f members"
sed -n '80,125p' "$f"
fi
done
echo
echo "IconButton definitions/usages nearby:"
rg -n "function IconButton|const IconButton|IconButton|DotsHorizontalIcon|aria-label|title=" web/sdk/admin | head -200 || trueRepository: raystack/frontier
Length of output: 16491
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read-only behavioral/structural probe for icon-only button accessibility indicators.
python3 - <<'PY'
import re, pathlib
xs = {
"web/sdk/admin/views/organizations/details/projects/columns.tsx": (188, 204),
"web/sdk/admin/views/organizations/details/projects/members/columns.tsx": (97, 113),
}
for path, (start, end) in xs.items():
text = pathlib.Path(path).read_text()
lines = text.splitlines()
block = "\n".join(lines[start-1:end])
print(f"--- {path}: lines {start}-{end}")
print(block)
print("contains aria-label/aria-labelledby:", bool(re.search(r"aria-(label|labelled-by)\s*=", block)))
print("contains title:", bool(re.search(r"\btitle\s*=", block)))
print("contains IconButton:", "IconButton" in block)
print("contains icon trigger:", bool(re.search(r"<(DotsHorizontalIcon)|<.*Icon", block, re.S)))
PYRepository: raystack/frontier
Length of output: 1552
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "IconButton packages/usages:"
rg -n 'from "`@raystack/apsara`"|from .*apsara.*|IconButton' package.json web/sdk/admin/package.json pnpm-lock.yaml package-lock.json yarn.lock 2>/dev/null || true
echo
rg -n 'export .*IconButton|const IconButton|function IconButton|IconButton =|IconButton(' . --glob '!node_modules' --glob '!dist' --glob '!build' | head -300
echo
echo "Imports in target files:"
sed -n '1,30p' web/sdk/admin/views/organizations/details/projects/columns.tsx
sed -n '1,30p' web/sdk/admin/views/organizations/details/projects/members/columns.tsxRepository: raystack/frontier
Length of output: 397
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Imports in target files:"
sed -n '1,30p' web/sdk/admin/views/organizations/details/projects/columns.tsx
sed -n '1,30p' web/sdk/admin/views/organizations/details/projects/members/columns.tsx
echo
echo "IconButton usages in target package paths:"
rg -n 'from "`@raystack/apsara`"|IconButton|title=/aria-label=|data-test-id="admin-project-(actions|member-action-menu)"' web/sdk/admin/views/organizations/details/projects/columns.tsx web/sdk/admin/views/organizations/details/projects/members/columns.tsx
echo
echo "Local IconButton-like component definitions excluding generated/package dirs:"
rg -n 'const IconButton|function IconButton|export .*IconButton|IconButton\s*=' web/sdk . --glob '!node_modules' --glob '!dist' --glob '!build' --glob '!*.lock' | head -200Repository: raystack/frontier
Length of output: 3299
🌐 Web query:
@raystack/apsara IconButton aria-label accessible name icon button documentation
💡 Result:
In the @raystack/apsara component library, the IconButton component is designed specifically for icon-only buttons [1][2]. Because these buttons lack visible text, providing an accessible name is mandatory to ensure assistive technologies can convey the button's purpose to users [3][1]. You must use the aria-label attribute to provide this accessible name [4][1]. Example usage: import { IconButton } from '@raystack/apsara'; import { TrashIcon } from '@radix-ui/react-icons'; function Actions { return ( ); } The documentation for IconButton highlights that this attribute is required for accessibility [1][2]. Additionally, the library advises considering the use of tooltips to complement the aria-label for improved usability [3]. As the IconButton component extends standard HTML button attributes, it also supports other standard ARIA attributes for managing state and behavior [3][1].
Citations:
- 1: https://raystack-apsara.mintlify.app/components/icon-button
- 2: https://mintlify.wiki/raystack/apsara/components/icon-button
- 3: https://apsara.raystack.org/docs/components/icon-button
- 4: https://raystack-apsara.mintlify.app/guides/accessibility
Add accessible names to both icon-only action triggers.
Both triggers render only DotsHorizontalIcon, so IconButton has no accessible name.
web/sdk/admin/views/organizations/details/projects/columns.tsx: addaria-label="Project actions".web/sdk/admin/views/organizations/details/projects/members/columns.tsx: addaria-label="Member actions".
📍 Affects 2 files
web/sdk/admin/views/organizations/details/projects/columns.tsx#L193-L199(this comment)web/sdk/admin/views/organizations/details/projects/members/columns.tsx#L102-L110
| <Dialog.Content | ||
| className={styles["dialog-content"]} | ||
| initialFocus={() => initialFocusRef.current} | ||
| > | ||
| <Dialog.Header> | ||
| {isProjectLoading ? ( | ||
| <Skeleton containerClassName={styles["flex1"]} width={"200px"} /> | ||
| ) : ( | ||
| <Dialog.Title>{project?.title ?? ""}</Dialog.Title> | ||
| )} | ||
| <Dialog.CloseButton data-test-id="close-button" /> | ||
| </Dialog.Header> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate and inspect the reviewed file around the dialog construction and close-control references.
file="web/sdk/admin/views/organizations/details/projects/members/index.tsx"
if [ -f "$file" ]; then
echo "== file exists =="
wc -l "$file"
echo
echo "== reviewed section =="
sed -n '160,250p' "$file" | nl -ba -v160
echo
echo "== dialog close button references in file =="
rg -n "Dialog\.CloseButton|CloseButton|initialFocus|Dismiss|onClick"| "$file" || true
else
echo "file not found: $file"
echo "candidate files:"
fd -i '.*members.*index\.tsx$|.*members.*ts$' web 2>/dev/null | head -50
fi
echo
echo "== project member dialogs usages =="
rg -n "ProjectMember|members" web/sdk/admin/views/organizations/details/projects -g '*.tsx' -g '*.ts' | head -120Repository: raystack/frontier
Length of output: 311
🏁 Script executed:
#!/bin/bash
set -euo pipefail
file="web/sdk/admin/views/organizations/details/projects/members/index.tsx"
echo "== reviewed section =="
sed -n '160,250p' "$file"
echo
echo "== dialog close button references in file =="
rg -n "Dialog\.CloseButton|CloseButton|initialFocus|Dismiss|onClick"| "$file" || true
echo
echo "== project member dialogs usages =="
rg -n "ProjectMember|members" web/sdk/admin/views/organizations/details/projects -g '*.tsx' -g '*.ts' | head -120
echo
echo "== Dialog implementation references =="
rg -n "CloseButton|class .*Dialog|function .*Dialog|const .*Dialog" web/sdk/admin web/sdk/shared -g '*.tsx' -g '*.ts' | head -200Repository: raystack/frontier
Length of output: 3205
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== dialog close button references in reviewed file =="
rg -n "Dialog\.CloseButton|CloseButton|initialFocus|Dismiss|onClick" web/sdk/admin/views/organizations/details/projects/members/index.tsx || true
echo
echo "== other dialog usages with close buttons =="
rg -n "Dialog\.CloseButton|<Dialog\.CloseButton|CloseButton" web/sdk/admin/views web/sdk/shared -g '*.tsx' -g '*.ts' | head -200
echo
echo "== Dialog component definition candidates =="
rg -n "function Dialog|const Dialog|export .*Dialog|Dialog = " web/sdk -g '*.tsx' -g '*.ts' | head -200Repository: raystack/frontier
Length of output: 10265
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate Dialog component and header button props =="
rg -n "interface .*Dialog|type .*Dialog|export namespace Dialog|ShowCloseButton|CloseButton|Header|HeaderClose" web/sdk -g '*.tsx' -g '*.ts' | head -250
echo
echo "== Dialog import path in reviewed file =="
sed -n '1,80p' web/sdk/admin/views/organizations/details/projects/members/index.tsx
echo
echo "== nearby dialogs with close buttons in admin =="
sed -n '1,80p' web/sdk/admin/views/users/details/security/security-sessions-revoke-session-confirm.tsx
sed -n '90,170p' web/sdk/admin/views/roles/index.tsx | sed -n '/<Drawer.Content/,/<\/Drawer>/p'Repository: raystack/frontier
Length of output: 28533
🌐 Web query:
raystack apsara Dialog CloseButton Dialog.Content showCloseButton API
💡 Result:
In the Raystack Apsara UI library, the handling of the dialog close button has evolved. Recent updates (specifically around the rc.8 release) deprecated or removed the showCloseButton prop [1]. Instead of using a prop on Dialog.Content to toggle visibility, the library now favors an explicit composition pattern using the Dialog.CloseButton component [2][3]. To include a close button in your dialog, you should explicitly place the Dialog.CloseButton component within your Dialog.Header [3]. Example usage: tsx import { Dialog } from '`@raystack/apsara`' <Dialog> <Dialog.Trigger /> <Dialog.Content> <Dialog.Header> <Dialog.Title>Title</Dialog.Title> <Dialog.CloseButton /> </Dialog.Header> <Dialog.Body> <Dialog.Description /> </Dialog.Body> <Dialog.Footer /> </Dialog.Content> </Dialog> The Dialog.CloseButton is a styled component that renders an X icon and is automatically positioned in the top-right corner of the dialog [2][4][3]. You should avoid looking for a showCloseButton API on Dialog.Content, as that approach has been superseded by this component-based architecture [1].
Citations:
- 1: fix: bump apsara to rc.8 and SDK dialog refactors #1627
- 2: https://raystack-apsara.mintlify.app/components/dialog
- 3: https://apsara.raystack.org/docs/components/dialog
- 4: https://mintlify.wiki/raystack/apsara/components/dialog
Restore a close control for the project-member dialog.
Dialog.CloseButton is the intended Apsara composition, and no other close action is present. Add it to the header, or replace the removed button with another labeled close trigger so users are not limited to keyboard/overlay dismissal.
Proposed fix
<Dialog.Header>
{isProjectLoading ? (
<Skeleton containerClassName={styles["flex1"]} width={"200px"} />
) : (
<Dialog.Title>{project?.title ?? ""}</Dialog.Title>
)}
+ <Dialog.CloseButton />
</Dialog.Header>📝 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.
| <Dialog.Content | |
| className={styles["dialog-content"]} | |
| initialFocus={() => initialFocusRef.current} | |
| > | |
| <Dialog.Header> | |
| {isProjectLoading ? ( | |
| <Skeleton containerClassName={styles["flex1"]} width={"200px"} /> | |
| ) : ( | |
| <Dialog.Title>{project?.title ?? ""}</Dialog.Title> | |
| )} | |
| <Dialog.CloseButton data-test-id="close-button" /> | |
| </Dialog.Header> | |
| <Dialog.Content | |
| className={styles["dialog-content"]} | |
| initialFocus={() => initialFocusRef.current} | |
| > | |
| <Dialog.Header> | |
| {isProjectLoading ? ( | |
| <Skeleton containerClassName={styles["flex1"]} width={"200px"} /> | |
| ) : ( | |
| <Dialog.Title>{project?.title ?? ""}</Dialog.Title> | |
| )} | |
| <Dialog.CloseButton /> | |
| </Dialog.Header> |
| .table-content-wrapper:focus, | ||
| .table-content-wrapper:focus-visible { | ||
| outline: none; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Focus outline removed without a replacement indicator.
.table-content-wrapper:focus/:focus-visible sets outline: none with no alternative visible focus style (e.g. box-shadow/outline on a different color). If this wrapper is keyboard-focusable (e.g., for scrolling), this removes the only visual indication of focus for keyboard users.
♿ Proposed fix to restore a visible focus indicator
.table-content-wrapper:focus,
.table-content-wrapper:focus-visible {
- outline: none;
+ outline: 2px solid var(--rs-color-border-focus, currentColor);
+ outline-offset: -2px;
}📝 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.
| .table-content-wrapper:focus, | |
| .table-content-wrapper:focus-visible { | |
| outline: none; | |
| } | |
| .table-content-wrapper:focus, | |
| .table-content-wrapper:focus-visible { | |
| outline: 2px solid var(--rs-color-border-focus, currentColor); | |
| outline-offset: -2px; | |
| } |
Summary
Design polish for the IAM Admin Console organization flow. This PR applies the outstanding design-review fixes to the org listing, detail tabs, and project members flow. Each design item is a self-contained commit for easy review.
No backend/Go changes — frontend only (
web/sdk/admin/views/organizations/*).Changes
Owner/Member/Admin.Technical Details
formatRoleTitle()helper (web/sdk/admin/utils/helper.ts) and used by the members columns.DataTable.ContentzeroState/emptyStateprops —zeroStaterenders when there's no data at all,emptyStateonly when a search returns nothing. Table headers auto-hide in both, so no extra handling was needed.opacity: 0by default and goes to1ontr:hover,:focus-within, and:has([data-popup-open])(keeps it visible while the menu is open and for keyboard users). The open row is highlighted viatr:has([data-test-id="…"][data-popup-open]).canAddMemberis derived fromorgMembersMap(> 1 member) and threaded through the project actions menu and the project-members dialog to disable the trigger.Test Plan
Manual checks
Owner/Member/Adminwith no prefix.SQL Safety (if your PR touches
*_repository.goorgoqu.*)Not applicable — this PR is frontend-only and touches no
*_repository.go/goqu.*code.Items fixed
list/navbar.tsx,list/list.module.cssformatRoleTitle()inutils/helper.ts+members/columns.tsxapis,invoices,members,projects,tokensindex + module.cssmembers,projects,projects/memberscolumns + module.cssprojects/columns.tsx,projects/index.tsx,projects/members/*invoices/columns.tsx,invoices.module.css