Skip to content

feat: add roles management dashboard (frontend-only) - #38

Open
rachid-hammami wants to merge 7 commits into
arhamkhnz:mainfrom
rachid-hammami:feature/roles-management
Open

feat: add roles management dashboard (frontend-only)#38
rachid-hammami wants to merge 7 commits into
arhamkhnz:mainfrom
rachid-hammami:feature/roles-management

Conversation

@rachid-hammami

@rachid-hammami rachid-hammami commented Feb 26, 2026

Copy link
Copy Markdown

Overview

This PR introduces a frontend-only Roles Management module under:

src/app/(main)/dashboard/roles/

The implementation follows the existing colocation architecture and remains fully isolated from any RBAC or backend logic.


Features

  • Roles listing powered by TanStack Table (pagination included)
  • Dialog-based create and edit flows
  • Reserved role name validation
  • Duplicate role name prevention
  • Secure ID generation using crypto.randomUUID()
  • Read-only permissions display
  • System roles protected from edit/delete
  • Sidebar integration (activated “Roles” link)

Architecture Notes

  • No imports from @/lib/rbac
  • No backend assumptions
  • Fully frontend-only
  • Designed to be RBAC-ready for future integration

Build

  • TypeScript strict mode
  • No lint errors
  • Production build passes

Screenshot

Capture

Greptile Summary

This PR adds a fully frontend-only Roles Management module under src/app/(main)/dashboard/roles/, activating the previously "coming soon" sidebar link. It introduces a TanStack Table-powered listing with pagination, dialog-based create/edit flows, system role protection, and a read-only permissions side panel — all wired through React state with no backend coupling.

The implementation is well-structured and follows the project's colocation conventions. Previous review concerns (reserved-name validation, crypto.randomUUID(), duplicate prevention, cursor-pointer on clickable cells) have all been addressed. Two minor improvements remain:

  • columns.tsx renders raw permission keys (e.g. users.read) as badge labels instead of the human-readable labels defined in permissions.ts, creating a visible inconsistency with the RolePermissions side panel which correctly maps them.
  • Column definitions passed to useReactTable in RolesTable.tsx are recreated on every render without memoisation; wrapping in useMemo (and useCallback for the parent handlers) would align with TanStack Table's recommended pattern and avoid unnecessary re-processing.

Confidence Score: 4/5

  • Safe to merge; the two remaining issues are minor UX and performance suggestions that don't block functionality.
  • All critical and blocking concerns from prior review rounds have been resolved. The two remaining issues — permission key labels and column memoisation — are P2 style/performance suggestions. The core feature works correctly as a frontend-only module.
  • columns.tsx (permission label display inconsistency) and RolesTable.tsx (column memoisation)

Important Files Changed

Filename Overview
src/app/(main)/dashboard/roles/page.tsx Main page component wiring all sub-components; canManage is hard-coded to true (intentional placeholder), and three leftover French emoji comments remain (already flagged in a prior review thread).
src/app/(main)/dashboard/roles/_components/AddRoleDialog.tsx Dialog for adding roles; uses crypto.randomUUID(), reserved-name validation, and duplicate-name prevention via Zod refine with memoised schema. Clean implementation.
src/app/(main)/dashboard/roles/_components/EditRoleDialog.tsx Dialog for editing roles; mirrors AddRoleDialog with correct reserved-name and duplicate-name validation (excluding the current role from uniqueness check). Addresses all previously flagged concerns.
src/app/(main)/dashboard/roles/_components/columns.tsx Column definitions for the roles table; renderPermissions renders raw permission keys (e.g. users.read) instead of their human-readable labels from permissions.ts, creating a UX inconsistency with the RolePermissions side panel.
src/app/(main)/dashboard/roles/_components/RolesTable.tsx TanStack Table wrapper with pagination and row selection; column definitions are recreated on every render without memoisation, which can cause unnecessary TanStack Table re-processing.
src/app/(main)/dashboard/roles/_components/RoleActions.tsx Dropdown menu with edit/delete actions, confirmation dialog, and tooltip hints for disabled system roles. Implementation is solid.
src/app/(main)/dashboard/roles/_components/RolePermissions.tsx Read-only permissions panel; correctly resolves permission keys to human-readable labels grouped by category.
src/app/(main)/dashboard/roles/_data/roles.ts Static demo role data with correct types; all four seeded roles are marked isSystem: true.
src/navigation/sidebar/sidebar-items.ts Activates the Roles sidebar link by updating the URL to /dashboard/roles and removing the comingSoon flag.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    Page["RolesPage\n(page.tsx)"]
    State["React State\nroles / selectedRole\neditingRole / isAddOpen"]
    Table["RolesTable\n(TanStack Table + pagination)"]
    Cols["getColumns\n(columns.tsx)"]
    Actions["RoleActions\n(Edit / Delete dropdown)"]
    AddDlg["AddRoleDialog\nZod validation\ncrypto.randomUUID()"]
    EditDlg["EditRoleDialog\nZod validation\nexclude-self duplicate check"]
    Perms["RolePermissions\nread-only side panel"]
    Data["_data/roles.ts\ndemoRoles seed"]
    Permsdata["_data/permissions.ts\npermission definitions"]

    Page --> State
    State --> Table
    State --> AddDlg
    State --> EditDlg
    State --> Perms
    Table --> Cols
    Cols --> Actions
    Actions -- "onEdit / onDelete" --> Page
    AddDlg -- "onConfirm(newRole)" --> State
    EditDlg -- "onConfirm(updatedRole)" --> State
    Table -- "onSelect(role)" --> State
    Data -- "initial seed" --> State
    Permsdata -- "label lookup" --> Perms
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/app/(main)/dashboard/roles/_components/RolesTable.tsx
Line: 673-687

Comment:
**Column definitions recreated on every render**

`getColumns(...)` is called inline inside `useReactTable`, which creates a brand-new array reference on every render. TanStack Table tracks column identity by reference; passing a new array each time can cause the table to unnecessarily re-process its internal state.

The TanStack Table docs recommend memoizing column definitions. Since `onEdit`, `onDelete`, and `canManage` are all re-created/passed by value on every render from the parent, wrapping in `useMemo` stabilises this:

```tsx
import * as React from "react"

// inside the component:
const columns = React.useMemo(
  () => getColumns({ canManage, onEdit, onDelete }),
  [canManage, onEdit, onDelete]
)

const table = useReactTable({
  data: roles,
  columns,
  ...
})
```

For best results, also wrap the `handleEdit` and `handleDelete` callbacks in `useCallback` in `page.tsx` so the memo dependency stays stable.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: src/app/(main)/dashboard/roles/_components/columns.tsx
Line: 824-843

Comment:
**Permission badges display raw keys instead of human-readable labels**

`renderPermissions` renders raw permission keys (e.g. `users.read`) as badge text. The `permissions.ts` data file has a `label` field (e.g. `"Read users"`) meant for display, and `RolePermissions.tsx` already maps keys to those labels correctly. This inconsistency means a user sees technical keys in the table column but friendly names in the side panel.

Fix by importing the `permissions` array and building a key → label lookup map, then replacing `{k}` in the badge with `permissionLabelMap[k] ?? k`.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (7): Last reviewed commit: "refactor(roles): improve visual hierarch..." | Re-trigger Greptile

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

12 files reviewed, 7 comments

Edit Code Review Agent Settings | Greptile

},
})

const onSubmit = async (values: any) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

using any type bypasses TypeScript type checking

Suggested change
const onSubmit = async (values: any) => {
const onSubmit = async (values: z.infer<typeof schema>) => {
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(main)/dashboard/roles/_components/EditRoleDialog.tsx
Line: 52

Comment:
using `any` type bypasses TypeScript type checking

```suggestion
  const onSubmit = async (values: z.infer<typeof schema>) => {
```

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/app/(main)/dashboard/roles/_components/EditRoleDialog.tsx Outdated
Comment thread src/app/(main)/dashboard/roles/_components/AddRoleDialog.tsx Outdated
Comment on lines +87 to +94
onClick={() => {
if (
cell.column.id === "name" ||
cell.column.id === "description"
) {
onSelect(row.original)
}
}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

add cursor-pointer class to the <td> element when the column is clickable (name or description) to indicate interactivity

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/(main)/dashboard/roles/_components/RolesTable.tsx
Line: 87-94

Comment:
add `cursor-pointer` class to the `<td>` element when the column is clickable (name or description) to indicate interactivity

<sub>Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!</sub>

How can I resolve this? If you propose a fix, please make it concise.

Comment thread src/app/(main)/dashboard/roles/page.tsx Outdated
Comment thread src/app/(main)/dashboard/roles/_components/AddRoleDialog.tsx Outdated
Comment thread src/app/(main)/dashboard/roles/page.tsx Outdated
@rachid-hammami

Copy link
Copy Markdown
Author

🚀 Role Management – Initial Implementation

This PR introduces the first implementation of the Role Management module.

Included

  • Roles table (TanStack Table)
  • Add / Edit role dialogs
  • Client-side validation (reserved names, duplicates)
  • Protected system roles
  • Clean and modular frontend structure

Scope

This implementation is frontend-only and follows the project’s existing architecture and UI patterns.

Ready for review.
Open to structural or architectural feedback.

@arhamkhnz

Copy link
Copy Markdown
Owner

Looks good overall. Try improving the design though, it feels too plain right now. Add some creativity and stronger visual hierarchy. If possible, try using this interface design skill with whatever AI tool you’re using: https://www.ui-skills.com/skills/interface-design/.

@rachid-hammami

Copy link
Copy Markdown
Author

Hi! I improved the visual hierarchy by adding header structure and role stats to make the page less plain and more aligned with dashboard patterns.

Let me know if you'd like any further adjustments 🙂

@renandeocleciano

Copy link
Copy Markdown

The skill change. This is the new https://www.ui-skills.com/skills/dammyjay93/interface-design

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