Skip to content

[Customer Portal][FE][Web] : Add Users tab to Project Details page#151

Merged
shayanmalinda merged 15 commits intowso2-open-operations:customer-portal-milestone-1from
KheHan07:customer-portal-milestone-1
Feb 15, 2026
Merged

[Customer Portal][FE][Web] : Add Users tab to Project Details page#151
shayanmalinda merged 15 commits intowso2-open-operations:customer-portal-milestone-1from
KheHan07:customer-portal-milestone-1

Conversation

@KheHan07
Copy link

@KheHan07 KheHan07 commented Feb 13, 2026

This pull request introduces the "Users" tab to the project details page, enabling the display and management of project users. It includes new UI components for listing users, adding new users via a dialog, and mock data support to facilitate development until backend APIs are available. The changes also add utility functions and constants to support user status handling.

Feature: Project Users Management

  • Added a new "Users" tab to the project details page, which displays a table of project users and allows adding/removing users. [1] [2] [3]
  • Implemented the ProjectUsersTab component to fetch, display, and locally manage the list of users, including skeleton loading, error handling, and user deletion (mocked).
  • Created the AddProjectUserDialog component, providing a multi-step dialog for entering user email and details, with validation and form reset logic.

API and Mock Data Support

  • Added the useGetProjectUsers hook to fetch project users, supporting mock data when mock mode is enabled.
  • Introduced mock data and types for project users in mockData.ts, used for development and testing.

User Status Utilities and Constants

  • Defined PROJECT_USER_TYPES constants for user status types, and implemented the getUserStatusColor utility for consistent status chip coloring in the UI. [1] [2] [3]

Summary by CodeRabbit

  • New Features
    • Added a "Users" tab on Project Details showing a table of project users (first name, last name, email, status).
    • Invite new project users via a two-step modal with email validation; invited users show "Invited" status.
    • Remove users from the project via an action in the table.
    • Color-coded status indicators and improved loading, empty, and error states.
    • Client-side fetching for project users with mock dataset and a new query key for project users.

@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a Project Users feature: mock project user model and dataset, a React Query hook to fetch users, a two-step Add Project User dialog, a Project Users tab with table/add/delete UI and status-colored chips, and integrates the tab into Project Details. (49 words)

Changes

Cohort / File(s) Summary
Data models & constants
apps/customer-portal/webapp/src/models/mockData.ts, apps/customer-portal/webapp/src/constants/projectDetailsConstants.tsx, apps/customer-portal/webapp/src/constants/apiConstants.ts
Adds MockProjectUser interface and mockProjectUsers dataset; registers new "users" tab and PROJECT_USER_STATUSES; adds ApiQueryKeys.PROJECT_USERS key ("project-users").
API hook
apps/customer-portal/webapp/src/api/useGetProjectUsers.ts
New React Query hook useGetProjectUsers(projectId) using query key [ApiQueryKeys.PROJECT_USERS, projectId]; returns mockProjectUsers when mock mode enabled, enabled only when projectId is truthy.
UI components
apps/customer-portal/webapp/src/components/project-details/users/AddProjectUserDialog.tsx, apps/customer-portal/webapp/src/components/project-details/users/ProjectUsersTab.tsx
Adds AddProjectUserDialog (two-step email → name form with validation) and ProjectUsersTab (fetches users, initializes local optimistic localUsers, supports add/delete UI, loading/error/empty states, status-colored chips).
Utilities
apps/customer-portal/webapp/src/utils/projectStats.ts
Adds getUserStatusColor(status) mapping user status strings to Chip color tokens (registeredsuccess, invitedwarning, default→default).
Integration
apps/customer-portal/webapp/src/pages/ProjectDetails.tsx
Imports and renders ProjectUsersTab when active tab id is "users".

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as ProjectUsersTab
    participant Hook as useGetProjectUsers
    participant State as LocalState
    participant Dialog as AddProjectUserDialog

    User->>UI: open "Users" tab
    UI->>Hook: fetch([ApiQueryKeys.PROJECT_USERS, projectId])
    Hook-->>UI: return mockProjectUsers
    UI->>State: initialize localUsers
    UI-->>User: render users table

    User->>UI: click "Add Project User"
    UI->>Dialog: open()
    User->>Dialog: enter email → Next
    Dialog->>Dialog: validate email
    User->>Dialog: enter first/last name → Done
    Dialog-->>UI: onSubmit(email, firstName, lastName)
    UI->>State: append new user (status: "Invited")
    UI-->>User: update table
    Dialog-->>UI: close()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

Type/New Feature, Type/UX, App/Customer Portal, Area/Frontend, Platform/Web

Suggested reviewers

  • Rashmika998
  • cloby99

Poem

🐰 I hopped through code with nimble paws,
New users, chips, and dialog laws.
Emails, names, a gentle invite,
Mocked friends join the table bright.
Hooray — the Users tab takes flight!

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description provides a clear feature overview and technical details but is missing most template sections (Purpose with issue links, Goals, Approach with screenshots, User stories, Release notes, Documentation, Training, Certification, Marketing, tests, security checks, samples, related PRs, migrations, test environment, and learning). Complete the PR description by adding missing required sections from the template, particularly Purpose/Goals/Approach, test coverage, security checks, documentation impact, and test environment details.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (7 files):

⚔️ apps/customer-portal/backend/service.bal (content)
⚔️ apps/customer-portal/backend/types.bal (content)
⚔️ apps/customer-portal/webapp/src/constants/apiConstants.ts (content)
⚔️ apps/customer-portal/webapp/src/constants/projectDetailsConstants.tsx (content)
⚔️ apps/customer-portal/webapp/src/models/mockData.ts (content)
⚔️ apps/customer-portal/webapp/src/pages/ProjectDetails.tsx (content)
⚔️ apps/customer-portal/webapp/src/utils/projectStats.ts (content)

These conflicts must be resolved before merging into customer-portal-milestone-1.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and specifically describes the main change: adding a Users tab to the Project Details page, which aligns directly with the changeset focus.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@apps/customer-portal/webapp/src/components/project-details/users/ProjectUsersTab.tsx`:
- Around line 66-70: The render body currently calls setLocalUsers and
setInitialized when fetchedUsers exist, which is an anti-pattern; move that sync
into a useEffect that depends on fetchedUsers and initialized so state updates
happen after render. Import useEffect if missing, and inside a useEffect(() => {
if (fetchedUsers && !initialized) { setLocalUsers(fetchedUsers);
setInitialized(true); } }, [fetchedUsers, initialized]) perform the update;
reference the existing identifiers fetchedUsers, initialized, setLocalUsers, and
setInitialized to locate where to change.
- Around line 171-178: The Chip in ProjectUsersTab is using an invalid sx prop
sx={{ font: "caption" }} which does nothing; change that to sx={{ typography:
"caption" }} on the Chip component (the one rendering label={user.status}) so
MUI applies the caption typography variant; search for the Chip in
ProjectUsersTab that uses getUserStatusColor(user.status) to locate and update
the sx prop accordingly.
🧹 Nitpick comments (2)
apps/customer-portal/webapp/src/constants/projectDetailsConstants.tsx (1)

203-207: Inconsistent casing convention for PROJECT_USER_TYPES values.

All other status constants in this file use title-case values (e.g., SUBSCRIPTION_STATUS.EXPIRED = "Expired", CASE_STATUS.OPEN = "Open", SLA_STATUS.GOOD = "All Good"), but PROJECT_USER_TYPES uses lowercase ("invited", "registered"). The MockProjectUser.status type also uses title-case ("Invited" | "Registered"). While getUserStatusColor normalizes via toLowerCase() so it works at runtime, aligning casing with the rest of the constants improves consistency.

Suggested fix
 export const PROJECT_USER_TYPES = {
-  INVITED: "invited",
-  REGISTERED: "registered",
+  INVITED: "Invited",
+  REGISTERED: "Registered",
 } as const;
apps/customer-portal/webapp/src/models/mockData.ts (1)

1779-1808: Mock user names are not capitalized.

The firstName and lastName values are all lowercase (e.g., "nisal", "perera"), which will display as-is in the table. Consider capitalizing them for a polished demo appearance.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request introduces a Users tab to the project details page, enabling display and management of project users with mock data support. The feature includes UI components for listing users in a table, adding new users through a multi-step dialog, and temporary local state management for add/delete operations until backend APIs are available.

Changes:

  • Added a Users tab with table display of project users including status chips and delete functionality
  • Implemented a multi-step dialog for adding users with email validation
  • Added mock data support with useGetProjectUsers hook and PROJECT_USER_TYPES constants

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
apps/customer-portal/webapp/src/pages/ProjectDetails.tsx Integrated ProjectUsersTab component into the project details page routing
apps/customer-portal/webapp/src/constants/projectDetailsConstants.tsx Added Users tab configuration and PROJECT_USER_TYPES constants
apps/customer-portal/webapp/src/utils/projectStats.ts Added getUserStatusColor utility for status chip styling
apps/customer-portal/webapp/src/models/mockData.ts Added MockProjectUser interface and mockProjectUsers array
apps/customer-portal/webapp/src/components/project-details/users/ProjectUsersTab.tsx Implemented main users tab component with table, local state management, and user operations
apps/customer-portal/webapp/src/components/project-details/users/AddProjectUserDialog.tsx Implemented multi-step dialog for adding users with email validation
apps/customer-portal/webapp/src/api/useGetProjectUsers.ts Added React Query hook for fetching project users with mock support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 53 to 206
export default function ProjectUsersTab({
projectId,
}: ProjectUsersTabProps): JSX.Element {
const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
const [localUsers, setLocalUsers] = useState<MockProjectUser[]>([]);
const [initialized, setInitialized] = useState<boolean>(false);

const {
data: fetchedUsers,
isFetching,
error,
} = useGetProjectUsers(projectId);

// Sync fetched data to local state for add/delete operations
if (fetchedUsers && !initialized) {
setLocalUsers(fetchedUsers);
setInitialized(true);
}

const users = initialized ? localUsers : (fetchedUsers ?? []);

const handleDeleteUser = (userId: string): void => {
// TODO: Replace with actual API call to delete user
setLocalUsers((prevUsers) =>
prevUsers.filter((user) => user.id !== userId),
);
};

const handleAddUser = (newUser: {
email: string;
firstName: string;
lastName: string;
}): void => {
// TODO: Replace with API call to add user
const user: MockProjectUser = {
id: Date.now().toString(),
firstName: newUser.firstName || "--",
lastName: newUser.lastName || "--",
email: newUser.email,
status: "Invited",
};
setLocalUsers((prevUsers) => [...prevUsers, user]);
setIsDialogOpen(false);
};

const renderTableSkeleton = (): JSX.Element => (
<>
{[1, 2, 3].map((row) => (
<TableRow key={row}>
{[1, 2, 3, 4, 5].map((col) => (
<TableCell key={col}>
<Skeleton variant="text" width="80%" />
</TableCell>
))}
</TableRow>
))}
</>
);

return (
<Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
mb: 2,
}}
>
<Typography variant="h6">Project Users</Typography>
<Button
variant="contained"
color="primary"
startIcon={<Plus size={16} />}
onClick={() => setIsDialogOpen(true)}
>
Add Project User
</Button>
</Box>

<TableContainer component={Paper} variant="outlined">
<Table>
<TableHead>
<TableRow>
<TableCell>First Name</TableCell>
<TableCell>Last Name</TableCell>
<TableCell>Email</TableCell>
<TableCell>Status</TableCell>
<TableCell align="right">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{isFetching ? (
renderTableSkeleton()
) : error ? (
<TableRow>
<TableCell colSpan={5} align="center">
<ErrorIndicator entityName="project users" />
</TableCell>
</TableRow>
) : users.length === 0 ? (
<TableRow>
<TableCell colSpan={5} align="center">
<Typography
variant="body2"
color="text.secondary"
sx={{ py: 2 }}
>
No users found for this project.
</Typography>
</TableCell>
</TableRow>
) : (
users.map((user) => (
<TableRow key={user.id}>
<TableCell>{user.firstName}</TableCell>
<TableCell>{user.lastName}</TableCell>
<TableCell>{user.email}</TableCell>
<TableCell>
<Chip
label={user.status}
size="small"
variant="outlined"
color={getUserStatusColor(user.status)}
sx={{ font: "caption" }}
/>
</TableCell>
<TableCell align="right">
<Tooltip title="Remove user">
<IconButton
color="error"
size="small"
onClick={() => handleDeleteUser(user.id)}
>
<Trash size={18} />
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</TableContainer>

{/* Add Project User Dialog */}
<AddProjectUserDialog
open={isDialogOpen}
onClose={() => setIsDialogOpen(false)}
onSubmit={handleAddUser}
/>
</Box>
);
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Test coverage is missing for the new ProjectUsersTab component. The codebase has established test coverage for similar components (e.g., TimeTrackingStatCards has tests at apps/customer-portal/webapp/src/components/project-details/time-tracking/tests/TimeTrackingStatCards.test.tsx). Consider adding tests for ProjectUsersTab to cover user listing, adding users, deleting users, loading states, and error states.

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

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

FYI : Reviewers those tests will address later

Comment on lines 43 to 174
export default function AddProjectUserDialog({
open,
onClose,
onSubmit,
}: AddProjectUserDialogProps): JSX.Element {
const [step, setStep] = useState<1 | 2>(1);
const [email, setEmail] = useState<string>("");
const [firstName, setFirstName] = useState<string>("");
const [lastName, setLastName] = useState<string>("");
const [emailError, setEmailError] = useState<string>("");

const resetForm = (): void => {
setStep(1);
setEmail("");
setFirstName("");
setLastName("");
setEmailError("");
};

const handleClose = (): void => {
resetForm();
onClose();
};

const validateEmail = (value: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(value);
};

const handleNext = (): void => {
if (!email.trim()) {
setEmailError("Email is required.");
return;
}
if (!validateEmail(email.trim())) {
setEmailError("Please enter a valid email address.");
return;
}
setEmailError("");
setStep(2);
};

const handleDone = (): void => {
onSubmit({
email: email.trim(),
firstName: firstName.trim(),
lastName: lastName.trim(),
});
resetForm();
};

return (
<Dialog open={open} onClose={handleClose} maxWidth="sm" fullWidth>
<DialogTitle>
{step === 1 ? "Add Project User" : "User Details"}
</DialogTitle>
<DialogContent>
{step === 1 ? (
<Box sx={{ pt: 1 }}>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Enter the email address of the user you want to add to this
project.
</Typography>
<TextField
autoFocus
label="Email Address"
type="email"
fullWidth
required
value={email}
onChange={(e) => {
setEmail(e.target.value);
if (emailError) setEmailError("");
}}
error={!!emailError}
helperText={emailError}
onKeyDown={(e) => {
if (e.key === "Enter") handleNext();
}}
/>
</Box>
) : (
<Box sx={{ pt: 1 }}>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
Provide the name details for <strong>{email}</strong>.
</Typography>
<TextField
autoFocus
label="First Name"
fullWidth
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
sx={{ mb: 2 }}
/>
<TextField
label="Last Name"
fullWidth
value={lastName}
onChange={(e) => setLastName(e.target.value)}
/>
</Box>
)}
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
{step === 1 ? (
<>
<Button variant="text" color="secondary" onClick={handleClose}>
Cancel
</Button>
<Button
variant="contained"
color="primary"
onClick={handleNext}
disabled={!email.trim()}
>
Next
</Button>
</>
) : (
<>
<Button variant="text" color="secondary" onClick={() => setStep(1)}>
Back
</Button>
<Button variant="contained" color="primary" onClick={handleDone}>
Done
</Button>
</>
)}
</DialogActions>
</Dialog>
);
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Test coverage is missing for the new AddProjectUserDialog component. The codebase follows a pattern of testing UI components (as seen in other component tests). Consider adding tests to cover the multi-step dialog flow, email validation, form submission, form reset, and the back/cancel functionality.

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

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

FYI : Reviewers those tests will address later

Comment on lines 29 to 46
export default function useGetProjectUsers(
projectId: string,
): UseQueryResult<MockProjectUser[]> {
const { isMockEnabled } = useMockConfig();

return useQuery<MockProjectUser[]>({
queryKey: ["projectUsers", projectId],
queryFn: async () => {
if (isMockEnabled) {
return mockProjectUsers;
}

// TODO: Implement API call when endpoint is available
return [];
},
enabled: !!projectId,
});
}
Copy link

Copilot AI Feb 13, 2026

Choose a reason for hiding this comment

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

Test coverage is missing for the new useGetProjectUsers hook. The codebase has established test coverage for similar API hooks (e.g., useGetProjectDeployments has tests). Consider adding tests to verify mock data handling, API calls when mock is disabled, query key structure, and the enabled condition.

Copilot uses AI. Check for mistakes.

Choose a reason for hiding this comment

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

FYI : Reviewers those tests will address later

Copy link

Choose a reason for hiding this comment

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

+1

*/
export const getUserStatusColor = (
status: string,
): "primary" | "info" | "default" | "success" | "warning" | "error" => {
Copy link

Choose a reason for hiding this comment

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

These should be moved to a custom type. @dileepapeiris do that when you find the time to.

Copy link

@dileepapeiris dileepapeiris Feb 13, 2026

Choose a reason for hiding this comment

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

I will address this. Thank you.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In
`@apps/customer-portal/webapp/src/components/project-details/users/ProjectUsersTab.tsx`:
- Around line 147-148: The component currently shows the skeleton based on React
Query's isFetching which also becomes true on background refetches and hides
local optimistic state; change the conditional to use isLoading instead so the
skeleton only appears on the initial load. In ProjectUsersTab (the render block
that calls renderTableSkeleton), replace checks using isFetching with isLoading
(keeping the existing initialized guard intact) so the skeleton is shown only
when there is no cached data; ensure any references to isFetching in that render
path are updated to isLoading and that renderTableSkeleton remains called only
for the initial load.
🧹 Nitpick comments (1)
apps/customer-portal/webapp/src/components/project-details/users/ProjectUsersTab.tsx (1)

83-98: No duplicate-email guard before adding a user.

handleAddUser appends the new user unconditionally. If the same email is submitted twice, the table will show duplicate rows. A quick check against localUsers would improve the UX even while the backend is mocked.

Suggested guard
     const handleAddUser = (newUser: {
         email: string;
         firstName: string;
         lastName: string;
     }): void => {
+        const alreadyExists = localUsers.some(
+            (u) => u.email.toLowerCase() === newUser.email.trim().toLowerCase(),
+        );
+        if (alreadyExists) {
+            // Optionally show a toast/snackbar here
+            return;
+        }
         // TODO: Replace with API call to add user
         const user: MockProjectUser = {

shayanmalinda
shayanmalinda previously approved these changes Feb 15, 2026
@shayanmalinda shayanmalinda merged commit a4172b1 into wso2-open-operations:customer-portal-milestone-1 Feb 15, 2026
1 check passed
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.

5 participants