Skip to content

[Customer Portal][FE][Web] Add Support Statistics Component and API Integration - #77

Merged
Rashmika998 merged 31 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/support-stats
Feb 4, 2026
Merged

Rashmika998 merged 31 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/support-stats

Conversation

@dileepapeiris

@dileepapeiris dileepapeiris commented Jan 31, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

This pull request implements the API integration for the projects/{id}/stats/support endpoint to fetch and display support case statistics. It also introduces a dedicated statistics row in the UI with high-fidelity loading skeletons to enhance the user experience during data fetching.

ScreenRecordings and screenshots

Screen.Recording.2026-01-31.at.20.24.48.mov

Support Page Stats cards

**Light mode: **

image

**Dark mode: **

image

**Loading Skeletons: - Light mode: **

image

**Loading Skeletons: -Dark mode: **
image

**Loading Skeletons: -Animations: **

Screen.Recording.2026-01-31.at.20.28.12.mov

Goals

  • Provide a clear, high-level overview of support metrics (Active Cases, Resolved Cases, Session Chats, etc.) for individual projects.
  • Ensure a smooth visual transition using skeleton loaders while data is being retrieved.
  • Establish a standardized pattern for fetching and displaying project-specific metrics.

Approach

  • Data Fetching: Developed the useGetProjectSupportStats custom hook. It includes a simulated 800ms latency to test the UI's resilience and loading states, integrated with a logging mechanism for monitoring.
  • UI Architecture: Created the CasesOverviewStats.tsx component using a responsive grid layout. Each stat is housed in a card that handles its own internal loading and error states.
  • Visual Feedback: Implemented Skeleton components from Oxygen UI that mirror the exact dimensions of the stat cards to prevent layout shift.

User stories

  • Metric Visibility: As a user, I can see the number of active and resolved cases at a glance when viewing a project's support overview.
  • Perceived Performance: As a user, I see placeholder skeletons while statistics are loading, ensuring the app feels responsive even with network latency.

Automation tests

  • Unit tests:
    • Coverage for useGetProjectSupportStats hook to verify data mapping and error handling.
    • Component tests for CasesOverviewStats to ensure skeletons render during loading states and data displays correctly upon success.
  • Integration tests: Verified that the component correctly pulls the projectId from the route context to fetch the relevant stats.
image

Closes(#57)

Summary by CodeRabbit

  • New Features

    • Implemented full navigation with a project hub landing page displaying all projects
    • Added support statistics dashboard showing active cases, resolved cases, and session metrics
    • Added header actions including community access, theme toggle, and user profile
    • Added multiple project detail pages: Dashboard, Project Details, Updates, Security Center, Engagements, Legal Contracts, Community, Announcements, and Settings
    • Added interactive project cards with status and key statistics
  • Tests

    • Added comprehensive unit test coverage for components and pages

Introduce SupportPage at apps/customer-portal/webapp/src/pages/SupportPage.tsx. The new React/TypeScript page extracts projectId from the route (useParams), fetches support statistics via useGetProjectSupportStats, and renders CasesOverviewStats with loading and stats props. File includes Apache-2.0 license header.
Remove the sx={{ p: 4 }} prop from the top-level Box in ProjectPage.tsx to eliminate fixed padding around the page. Let the surrounding layout or container handle spacing to avoid redundant whitespace around the project header.
ProjectHub now imports and uses the useGetProjects hook instead of useSearchProjects to fetch project data. No other logic was changed; this aligns the page with the updated/standardized hook for retrieving projects.
Add a new test file for SupportPage (apps/customer-portal/webapp/src/pages/__tests__/SupportPage.test.tsx). Tests use Vitest and React Testing Library and include mocks for react-router useParams, @wso2/oxygen-ui components, oxygen icons, and the useGetProjectSupportStats hook. Includes two tests: one verifies the loading state (skeletons and FileText icon) and one verifies rendered statistics and labels when data is loaded.
Replace mocked hook useSearchProjects with useGetProjects in ProjectHub tests. Update the vi.mock import path and mock variable (mockUseSearchProjects -> mockUseGetProjects) and adjust all mockReturnValue calls to use the new mock so tests align with the renamed API hook.
Introduce ProjectSupportStats to apps/customer-portal/webapp/src/models/responses.ts to model project-level support metrics. The interface includes totalCases, activeChats, sessionChats, and resolvedChats, with JSDoc comments for each field to improve typing and documentation in the customer portal webapp.
Add getMockProjectSupportStats() to apps/customer-portal/webapp/src/models/mockFunctions.ts and import the ProjectSupportStats type. The new function returns randomized ProjectSupportStats (activeChats, resolvedChats, sessionChats, totalCases) for use in UI mock data and testing.
Introduce a new test file for the useGetProjects hook. Adds two tests verifying paginated responses and default pagination behavior using mockProjects, and asserts hook success state. Tests wrap the hook with a QueryClientProvider (retry disabled) and mock the useLogger hook to avoid logging side effects.
Introduce a new test suite for the useGetProjectSupportStats hook using vitest and @testing-library/react. Tests cover initial loading state, successful data fetch, and behavior when no project ID is provided. A mock logger is injected via vi.mock and tests run inside a QueryClientProvider to disable retries. File includes project license header.
Delete the unit test file apps/customer-portal/webapp/src/api/__tests__/useSearchProjects.test.tsx which contained renderHook/react-query tests for useSearchProjects. Removes the corresponding test coverage; no other code changes included in this changeset.
Introduce useGetProjects, a React Query infinite query hook for fetching paginated projects in the customer-portal webapp. Accepts SearchProjectsRequest and a fetchAll flag (uses larger limit and a stable cache key for shared "all projects" queries). Current implementation returns mocked data (mockProjects) with an 800ms simulated latency for development/demo use, uses a default page size of 10 (100 when fetchAll is true), and computes next/previous page offsets; results are cached indefinitely (staleTime: Infinity). Replace or wire this hook to the real backend API when available.
Introduce useGetProjectSupportStats React Query hook (apps/customer-portal/webapp/src/api/useGetProjectSupportStats.ts). The hook returns UseQueryResult<ProjectSupportStats, Error>, uses getMockProjectSupportStats with an 800ms simulated latency, and logs activity via useLogger. Query key is [ApiQueryKeys.SUPPORT_STATS, id], the query is enabled only when id is truthy, and staleTime is set to 5 minutes (TODO: adjust). This is a development/demo mock and should be replaced when wiring to the real backend.
Import the new SupportPage component and update the /support route to render SupportPage instead of reusing ProjectPage. This provides a dedicated page for support-related UI and separates concerns in App.tsx (apps/customer-portal/webapp/src/App.tsx).
Delete apps/customer-portal/webapp/src/api/useSearchProjects.ts. This file implemented a mock-based React Query infinite query hook for searching projects (used mockProjects, simulated ~800ms latency, and returned paginated SearchProjectsResponse). Remove the legacy/mock implementation so callers can rely on a real API-backed hook or alternate implementation.
Fix Header.test.tsx mock import: change vi.mock from '@/api/useSearchProjects' to '@/api/useGetProjects' so the test mocks the correct module (mock implementation unchanged). This aligns the test with the renamed/relocated API hook.
Add apps/customer-portal/webapp/src/constants/supportConstants.ts defining a SupportStatConfig interface and exporting SUPPORT_STAT_CONFIGS. The new file maps ProjectSupportStats keys to icon components, colors, labels and optional secondary icons (imports oxygen-ui icons) and includes the project license header.
Introduce a new SUPPORT_STATS key ("support-stats") in ApiQueryKeys within apps/customer-portal/webapp/src/constants/apiQueryKeys.ts to provide a dedicated query key for project support statistics API calls.
Introduce a new CasesOverviewStats React component to render a responsive grid of support statistic cards. The component accepts isLoading and stats (ProjectSupportStats) props, maps SUPPORT_STAT_CONFIGS to StatCard items, displays a Skeleton while loading, overlays optional secondary icons, and falls back to 0 for missing values. Uses @wso2/oxygen-ui Box, Grid, StatCard and Skeleton components.
Remove unused icon imports (CircleAlert, MessageSquareDiff, MessageSquareMore) from apps/customer-portal/webapp/src/constants/supportConstants.ts to clean up imports and avoid unused symbols.
Add unit tests for CasesOverviewStats at apps/customer-portal/webapp/src/components/support/__tests__/CasesOverviewStats.test.tsx. Mocks @wso2/oxygen-ui and @wso2/oxygen-ui-icons-react, and verifies both loading state (skeletons and icons) and loaded state (renders stat values and labels). Tests use Vitest and React Testing Library.
Replace the React.FC arrow component with an exported default function for ProjectCard. Remove the unused default React import (keeping only the JSX type) and eliminate the redundant export at the file end. Typing and return signature (ProjectCardProps -> JSX.Element) are preserved; no functional changes intended.
Replace the deprecated useSearchProjects hook with useGetProjects in apps/customer-portal/webapp/src/components/header/Header.tsx. Updated the import and hook call to useGetProjects({}, true) so the Header component uses the new project-fetching hook/naming.
Add a mocked useLogger and extend SupportPage tests to validate logging behavior. Introduces mockLogger (debug/error/info/warn) via vi.mock, adjusts the loading test to expect 4 skeletons and persistent icon, adds an error-state test that checks the error message and that mockLogger.error is called with the project id, and asserts mockLogger.debug is called when data loads. This ensures log calls are validated alongside UI expectations.
Introduce logging and error UI for project support stats: add useLogger and useEffect hooks to log fetch errors and successful loads, include isError from useGetProjectSupportStats, and render a centered error message using Box and Typography when fetching fails. Also import the necessary UI components and expand the data fetch handling for better observability and user feedback.
@dileepapeiris dileepapeiris self-assigned this Jan 31, 2026
@coderabbitai

coderabbitai Bot commented Jan 31, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR introduces a fully functional customer portal web application featuring React Router-based navigation, new data-fetching hooks for projects and support statistics, page components (ProjectHub, ProjectPage, SupportPage), header UI components, and comprehensive type definitions and mocks. The placeholder App component is replaced with a routed architecture mapping to multiple titled project pages.

Changes

Cohort / File(s) Summary
App Router Setup
apps/customer-portal/webapp/src/App.tsx
Replaces placeholder with fully wired React Router layout; adds nested routes for ProjectHub root, dynamic/:projectId subtree with multiple ProjectPage variants (Dashboard, Details, Updates, Security, Engagements, Contracts, Community, Announcements, Settings), Support page, and fallback redirect.
API Data-Fetching Hooks
apps/customer-portal/webapp/src/api/useGetProjects.ts, apps/customer-portal/webapp/src/api/useGetProjectSupportStats.ts
Introduces useGetProjects with infinite pagination and useGetProjectSupportStats for mock data fetching via React Query; includes logging, artificial latency for development, and stable query keys.
Page Components
apps/customer-portal/webapp/src/pages/ProjectHub.tsx, apps/customer-portal/webapp/src/pages/ProjectPage.tsx, apps/customer-portal/webapp/src/pages/SupportPage.tsx
Adds three page components: ProjectHub (fetches and displays project cards), ProjectPage (generic titled page with projectId), SupportPage (fetches and displays support statistics via CasesOverviewStats).
Header & Navigation Components
apps/customer-portal/webapp/src/components/header/Header.tsx, apps/customer-portal/webapp/src/components/header/Actions.tsx
Updates Header to use useGetProjects with memoized project flattening; adds Actions component with community link, theme toggle, divider, and user profile.
Support Statistics UI
apps/customer-portal/webapp/src/components/support/CasesOverviewStats.tsx
Renders responsive grid of stat cards with loading skeletons, icons, and optional secondary icons; configurable via SUPPORT_STAT_CONFIGS.
Project Card Component
apps/customer-portal/webapp/src/components/projectCard/ProjectCard.tsx
Clickable card component displaying project metadata (id, key, title, subtitle, date, status, openCases, activeChats) with nested badge, info, stats, and actions subcomponents; navigates to /{id}/dashboard on click.
Models & Constants
apps/customer-portal/webapp/src/models/responses.ts, apps/customer-portal/webapp/src/models/mockFunctions.ts, apps/customer-portal/webapp/src/constants/apiQueryKeys.ts, apps/customer-portal/webapp/src/constants/supportConstants.ts
Adds TypeScript interfaces (ProjectListItem, SearchProjectsResponse, UserProfile, ProjectSupportStats), mock data helper functions (getMockStatus, getMockOpenCases, getMockActiveChats, getMockProjectSupportStats), API query key constants, and support stat configuration array.
Comprehensive Test Suite
apps/customer-portal/webapp/src/api/__tests__/\*, apps/customer-portal/webapp/src/components/header/__tests__/\*, apps/customer-portal/webapp/src/components/support/__tests__/\*, apps/customer-portal/webapp/src/pages/__tests__/\*
Adds 10 test files covering loading/success/error states for hooks (useGetProjects, useGetProjectSupportStats), components (Header, Actions, CasesOverviewStats), and pages (ProjectHub, SupportPage); mocks UI library and internal dependencies.

Sequence Diagram

sequenceDiagram
    actor User
    participant Router as React Router
    participant Page as Page Component
    participant Hook as Data Hook
    participant QueryClient as React Query
    participant MockAPI as Mock Data Service
    participant UI as UI Renderer

    User->>Router: Navigate to /projects
    Router->>Page: Render ProjectHub
    Page->>Hook: useGetProjects()
    Hook->>QueryClient: Execute useInfiniteQuery
    QueryClient->>MockAPI: Fetch projects (simulated 800ms)
    MockAPI-->>QueryClient: Return SearchProjectsResponse
    QueryClient-->>Hook: Return paginated data
    Hook-->>Page: Pass projects, loading state
    Page->>UI: Render ProjectCard grid
    UI-->>User: Display projects

    User->>Router: Click project → /project-1/support
    Router->>Page: Render SupportPage
    Page->>Hook: useGetProjectSupportStats(projectId)
    Hook->>QueryClient: Execute useQuery
    QueryClient->>MockAPI: Fetch support stats (simulated 800ms)
    MockAPI-->>QueryClient: Return ProjectSupportStats
    QueryClient-->>Hook: Return stats data
    Hook-->>Page: Pass stats, loading state
    Page->>UI: Render CasesOverviewStats
    UI-->>User: Display support metrics
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

Type/New Feature, Type/UX

Suggested reviewers

  • v15a1
  • cloby99

🐰 Hop, hop, hooray! A portal springs to life,
With routes and cards and stats so rife,
Mock data flows like morning dew,
React Query knows just what to do! ✨
The customer journey now shines bright and true! 🌟

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding support statistics component and API integration for the customer portal frontend.
Description check ✅ Passed The PR description is comprehensive and covers most required sections: Purpose, Goals, Approach, User stories, Automation tests, and includes visual evidence. However, some template sections are missing.
Docstring Coverage ✅ Passed Docstring coverage is 94.12% 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 docstrings
🧪 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.

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

🤖 Fix all issues with AI agents
In
`@apps/customer-portal/webapp/src/api/__tests__/useGetProjectSupportStats.test.tsx`:
- Around line 32-43: The QueryClient is shared across tests causing cache
pollution and flakiness; fix by creating/resetting a fresh QueryClient before
each test using vitest's beforeEach (imported from 'vitest') and re-creating the
wrapper that uses that QueryClient; specifically, move the QueryClient
construction and the wrapper ({ children } => <QueryClientProvider
client={queryClient}>{children}</QueryClientProvider>) into a beforeEach so
queryClient is new per test (or call queryClient.clear() in beforeEach) to
ensure cached responses from previous tests don't affect
useGetProjectSupportStats tests.

In `@apps/customer-portal/webapp/src/components/header/Actions.tsx`:
- Around line 37-45: The Join our community Button that uses JOIN_COMMUNITY_URL
should open the external site in a new tab and prevent reverse tabnabbing:
update the Button (in Actions.tsx where Button is rendered with startIcon and
href={JOIN_COMMUNITY_URL}) to include target="_blank" and rel="noopener
noreferrer" attributes so the external page cannot access window.opener.

In `@apps/customer-portal/webapp/src/components/header/Header.tsx`:
- Around line 110-133: The effect re-runs every render because the derived
projects array (created via flatMap) has a new reference each render; memoize
that array with useMemo (e.g., const memoizedProjects = useMemo(() =>
projectsFlatMapLogic..., [rawProjectsDep1, rawProjectsDep2])) and then replace
projects with memoizedProjects in the useEffect dependency list (keeping
projectId and selectedProject?.id), or alternatively remove the derived array
from deps and depend on the raw source used to build it; update references in
the effect to use memoizedProjects and keep setProject, projectId,
selectedProject checks unchanged.

In `@apps/customer-portal/webapp/src/components/projectCard/ProjectCard.tsx`:
- Around line 78-88: The component ProjectCard currently uses mock functions
getMockStatus, getMockOpenCases, and getMockActiveChats as default parameter
values for props (status, openCases, activeChats); remove these mock defaults
and replace them with sensible static defaults (e.g., status = "Unknown",
openCases = 0, activeChats = 0) or make the props required in ProjectCardProps
so callers must pass real values, then delete the now-unused
getMockStatus/getMockOpenCases/getMockActiveChats imports; update the function
signature in ProjectCard to use the new defaults or required types and adjust
any callers/tests that relied on the mocks.

In `@apps/customer-portal/webapp/src/components/sideNavBar/SideBar.tsx`:
- Around line 78-91: The Link generation currently interpolates projectId (typed
as projectId?: string) into the URL which can produce "/undefined/..." during
transitions; update the Sidebar rendering logic around the Link that uses
NavigateLink so it only renders the Link (or a non-clickable fallback) when
projectId is defined. Locate the block using projectId in SideBar.tsx (the Link
with component={NavigateLink} and to={`/${projectId}/${item.path}`}) and wrap it
with a guard like "if (!projectId) return null" or render an inert element (no
NavigateLink) to avoid creating malformed links; ensure Sidebar.Item,
Sidebar.ItemIcon, and Sidebar.ItemLabel are preserved in the fallback so layout
remains stable.

In `@apps/customer-portal/webapp/src/pages/__tests__/SupportPage.test.tsx`:
- Around line 27-35: Tests share the mockLogger instance so its vi.fn() call
counts persist across tests; add a beforeEach to reset mocks (e.g.,
vi.clearAllMocks()) after the mock definitions to ensure each test starts with
fresh mock state. Locate the mockLogger and useLogger mock setup and insert a
beforeEach that clears the mocks so assertions like the loading state test's
check of mockLogger.debug are reliable.

In `@apps/customer-portal/webapp/src/pages/ProjectPage.tsx`:
- Line 41: The current destructuring const { projectId } = useParams<{
projectId: string }>() can yield undefined; update ProjectPage to defensively
handle missing projectId by adding an early guard after that line (e.g., if
(!projectId) return an error/NotFound UI or redirect) and ensure any downstream
uses of projectId (data fetching hooks, render logic) only run when projectId is
present; modify affected functions/components in this file (references to
projectId, fetchProject, useEffect hooks) to expect a defined string or be
skipped until projectId exists.
🧹 Nitpick comments (19)
apps/customer-portal/webapp/src/components/header/SearchBar.tsx (1)

25-33: Confirm accessible labeling for the search input.
If SearchBarUI doesn’t render a label, add an explicit aria-label (or equivalent) to avoid a placeholder-only label.

♿️ Proposed tweak (if supported by the component)
       <SearchBarUI
         size="small"
         placeholder="Search cases, tickets, or users"
+        aria-label="Search"
         sx={{ minWidth: 400 }}
       />
apps/customer-portal/webapp/src/components/projectCard/ProjectCardSkeleton.tsx (1)

33-33: Consider using function declaration for consistency.

Other components in this PR (e.g., ProjectCardActions, Brand) use function declarations (function ComponentName()) while this uses an arrow function. Consider aligning the style for consistency across the codebase.

♻️ Optional: Convert to function declaration
-const ProjectCardSkeleton = (): JSX.Element => {
-  return (
+export default function ProjectCardSkeleton(): JSX.Element {
+  return (
     <Card
     ...
   );
-};
-
-export default ProjectCardSkeleton;
+}
apps/customer-portal/webapp/src/utils/projectCard.ts (2)

35-37: Consider i18n for date formatting.

The locale is hardcoded to "en-US". If the application needs to support internationalization, consider using the user's locale or a configurable locale setting.

Example using browser locale
-    const month = date.toLocaleString("en-US", { month: "short" });
+    const month = date.toLocaleString(undefined, { month: "short" });

Or use a centralized i18n configuration if available.


51-64: Consider extracting status strings to constants.

The status strings ("All Good", "Need Attention", "Critical Issues") are hardcoded. If these are used elsewhere or could change, extracting them to a shared constants file would improve maintainability.

apps/customer-portal/webapp/src/components/support/CasesOverviewStats.tsx (1)

85-91: Unsafe type cast for loading skeleton.

The Skeleton component is cast to unknown as number to satisfy StatCard's value prop type. This is a type safety workaround that could mask issues.

Consider whether StatCard should accept ReactNode for its value prop, or if a dedicated loading state prop would be cleaner.

Alternative approaches

Option 1: If you control StatCard, update its value prop to accept ReactNode:

interface StatCardProps {
  value: number | ReactNode;
  // ...
}

Option 2: Use a wrapper component that handles loading internally:

<StatCard
  label={stat.label}
  value={stats?.[stat.key] ?? 0}
  icon={<stat.icon />}
  iconColor={stat.iconColor}
  isLoading={isLoading}
/>
apps/customer-portal/webapp/src/constants/appLayoutConstants.ts (1)

33-50: Consider exporting AppShellNavItem interface.

The AppShellNavItem interface is not exported. If consumer components (like SideBar) need to type their props based on this interface, exporting it would improve type safety.

Proposed change
-interface AppShellNavItem {
+export interface AppShellNavItem {
apps/customer-portal/webapp/src/components/header/UserProfile.tsx (1)

39-49: Consider removing unnecessary fragment wrapper.

The fragment <></> wrapper is unnecessary since UserMenu is the only child element. You can return the UserMenu directly.

♻️ Suggested simplification
   return (
-    <>
-      {/* user profile menu */}
-      <UserMenu
-        user={mockUser}
-        onProfileClick={() => logger.debug("Profile clicked")}
-        onSettingsClick={() => logger.debug("Settings clicked")}
-        onLogout={() => navigate("/")}
-      />
-    </>
+    <UserMenu
+      user={mockUser}
+      onProfileClick={() => logger.debug("Profile clicked")}
+      onSettingsClick={() => logger.debug("Settings clicked")}
+      onLogout={() => navigate("/")}
+    />
   );
apps/customer-portal/webapp/src/models/mockData.ts (1)

79-84: Minor: Email capitalization inconsistency.

The mock email "John@example.com" uses an unusual capitalization. While email addresses are case-insensitive for the domain part, it's conventional to use lowercase for mock/test data to avoid potential issues with case-sensitive comparisons in tests or UI displays.

♻️ Suggested fix
 export const mockUser: UserProfile = {
   name: "John Doe",
-  email: "John@example.com",
+  email: "john@example.com",
   avatar: "JD",
   role: "Admin",
 };
apps/customer-portal/webapp/src/pages/ProjectHub.tsx (4)

63-67: Consider logging only on initial load, not on every re-render.

The debug log will fire every time projects.length or logger changes. Since projects is re-derived on each render via flatMap, this effect could fire more frequently than intended. Consider using a ref to track if logging has occurred to avoid duplicate logs.

♻️ Suggested improvement using a ref
+import { useEffect, useRef, type JSX } from "react";
-import { useEffect, type JSX } from "react";
...

+  const hasLoggedProjects = useRef(false);
+
   useEffect(() => {
-    if (projects.length > 0) {
+    if (projects.length > 0 && !hasLoggedProjects.current) {
       logger.debug(`${projects.length} projects loaded in ProjectHub`);
+      hasLoggedProjects.current = true;
     }
-  }, [projects.length, logger]);
+  }, [projects.length, logger]);

74-108: Extract duplicated responsive grid styles.

The responsive flex styling for the card grid is duplicated between the loading skeleton and the loaded state (lines 76-106 and lines 134-172). Consider extracting this to a shared style constant or a wrapper component to improve maintainability.

♻️ Suggested approach
const cardGridSx = {
  display: "flex",
  flexWrap: "wrap",
  justifyContent: "center",
  gap: 3,
  maxWidth: 1800,
  mx: "auto",
  width: "100%",
};

const cardWrapperSx = {
  flex: {
    xs: "1 1 100%",
    sm: "0 1 calc(50% - 24px)",
    md: "0 1 calc(33.33% - 24px)",
    lg: "0 1 calc(25% - 24px)",
    xl: "0 1 calc(20% - 24px)",
  },
  maxWidth: { xs: "100%", sm: 400 },
  minWidth: 300,
};

Then reuse cardGridSx and cardWrapperSx in both the loading and loaded states.

Also applies to: 133-174


111-120: Stale comment: "Log error if projects are not loaded."

The comment says "Log error" but this block renders the error UI, not logging. The logging is handled in the earlier useEffect. Update the comment to reflect the actual behavior.

✏️ Suggested fix
-    /**
-     * Log error if projects are not loaded.
-     */
+    /**
+     * Render error state if loading failed.
+     */
     if (isError) {

122-131: Stale comment: "Log error if projects are not loaded."

Same issue—this comment describes logging but the code renders an empty state message. Update to match the actual behavior.

✏️ Suggested fix
-    /**
-     * Log error if projects are not loaded.
-     */
+    /**
+     * Render empty state if no projects are available.
+     */
     if (!projects || projects.length === 0) {
apps/customer-portal/webapp/src/pages/SupportPage.tsx (1)

38-47: Consider handling missing projectId explicitly.

When projectId is undefined, passing an empty string disables the query (via enabled: !!id in the hook), but the component will show an indefinite loading state rather than an error. Consider adding an explicit check for missing projectId to provide a clearer user experience.

♻️ Suggested improvement
+  if (!projectId) {
+    return (
+      <Box sx={{ mt: 3, textAlign: "center" }}>
+        <Typography variant="h6" color="error">
+          Project ID is required.
+        </Typography>
+      </Box>
+    );
+  }
+
   const {
     data: stats,
     isLoading,
     isError,
-  } = useGetProjectSupportStats(projectId || "");
+  } = useGetProjectSupportStats(projectId);
apps/customer-portal/webapp/src/components/header/__tests__/Header.test.tsx (2)

106-120: Consider renaming mockUseSearchProjects to mockUseGetProjects for clarity.

The mock variable is named mockUseSearchProjects but it mocks the useGetProjects hook (as seen in the vi.mock on line 117). This inconsistency could cause confusion during maintenance.

🔧 Suggested rename
-const mockUseSearchProjects = vi.fn(() => ({
+const mockUseGetProjects = vi.fn(() => ({
   data: {
     pages: [{ projects: mockProjects }],
   },
   fetchNextPage: mockFetchNextPage,
   hasNextPage: false,
   isFetchingNextPage: false,
   isError: false,
 })) as any;

 vi.mock("@/api/useGetProjects", () => ({
   default: (searchData: any, fetchAll: any) =>
-    mockUseSearchProjects(searchData, fetchAll),
+    mockUseGetProjects(searchData, fetchAll),
 }));

Also update references in beforeEach (line 169) and test cases (lines 247, 261).


235-244: Test case provides limited coverage for the useEffect clearing logic.

The comment on lines 241-242 acknowledges that the switcher isn't rendered on the hub page, so this test only verifies that the switcher is absent. It doesn't directly verify the useEffect state-clearing logic mentioned in the comment. Consider adding a test that renders on a project page first, then simulates navigation to the hub to verify state transitions.

apps/customer-portal/webapp/src/components/projectCard/ProjectCardStats.tsx (1)

95-97: Hardcoded color may not adapt to theme changes.

Using colors.blue[500] directly bypasses the theme system. If the app supports light/dark modes or custom themes, this color won't adjust accordingly. Consider using a theme-aware color like "info.main" or "secondary.main" for consistency.

🎨 Theme-aware alternative
-          <Typography variant="body2" color={colors.blue[500]}>
+          <Typography variant="body2" color="info.main">
             {activeChats}
           </Typography>
apps/customer-portal/webapp/src/models/mockFunctions.ts (1)

63-70: Inconsistent mock data generation approach.

getMockProjectSupportStats uses inline Math.random() with hardcoded ranges, while the other functions (getMockStatus, getMockOpenCases, getMockActiveChats) select from predefined arrays in mockData.ts. For consistency and easier maintenance, consider defining similar option arrays for support stats or extracting a shared random selection helper.

apps/customer-portal/webapp/src/components/sideNavBar/SideBar.tsx (1)

59-64: activeItem extraction may fail for trailing slashes.

location.pathname.split("/").pop() returns an empty string for paths like /projectId/dashboard/, causing activeItem to default to "dashboard" via the || "dashboard" fallback. However, this fallback may mask actual navigation issues. Consider using a more robust approach:

🔧 More robust path extraction
-  const activeItem = location.pathname.split("/").pop() || "dashboard";
+  const pathSegments = location.pathname.split("/").filter(Boolean);
+  const activeItem = pathSegments[pathSegments.length - 1] || "dashboard";
apps/customer-portal/webapp/src/api/useGetProjects.ts (1)

36-48: searchData parameter is accepted but not used in the query function.

The hook accepts searchData: SearchProjectsRequest but the queryFn doesn't filter or search based on it — it always returns sliced mockProjects. This is fine for the mock implementation, but consider adding a TODO comment to remind developers to implement actual search/filter logic when wiring to the real API.

📝 Add TODO for future implementation
       /**
        * Mock behavior: simulate network latency for the in-memory `mockProjects` data.
        * This is intended only for development/demo use and should be removed or
        * replaced when wiring this hook to the real backend API.
+       *
+       * TODO: Implement actual search/filter logic using `searchData` when
+       * connecting to the real backend API.
        */

Comment thread apps/customer-portal/webapp/src/components/header/Actions.tsx
Comment thread apps/customer-portal/webapp/src/components/header/Header.tsx
Comment thread apps/customer-portal/webapp/src/components/sideNavBar/SideBar.tsx
Comment thread apps/customer-portal/webapp/src/pages/ProjectPage.tsx

Copilot AI 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.

Pull request overview

This pull request adds API integration for project support statistics and implements case statistics cards with loading states. The changes introduce routing with react-router, create support statistics components, and add comprehensive test coverage.

Changes:

  • Added react-router integration for navigation between project hub and project-specific pages
  • Implemented API hooks for fetching projects and support statistics with mock data
  • Created case overview statistics cards with loading skeletons and error handling

Reviewed changes

Copilot reviewed 59 out of 60 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
package.json Added react-router and @asgardeo/react-router dependencies
vite.config.ts Configured vitest with CSS support and dependency inlining
src/utils/projectCard.ts Utility functions for date formatting and status color mapping
src/models/responses.ts TypeScript interfaces for API response types
src/models/requests.ts TypeScript interfaces for API request types
src/models/mockData.ts Mock data for projects and user profiles
src/models/mockFunctions.ts Helper functions for generating mock statistics
src/constants/*.ts Application constants for API keys, navigation, and support stats
src/api/*.ts React Query hooks for fetching projects and support statistics
src/components/support/* Support statistics display components with loading states
src/components/projectCard/* Project card components with skeleton loading
src/components/header/* Header components including search, project switcher, and user profile
src/components/footer/Footer.tsx Application footer with company info and links
src/components/sideNavBar/* Sidebar navigation with subscription widget
src/pages/*.tsx Page components for support, project details, and project hub
src/layouts/AppLayout.tsx Main layout with conditional sidebar rendering
src/App.tsx Application routing configuration
All test files Comprehensive unit tests for components, utilities, and API hooks
Files not reviewed (1)
  • apps/customer-portal/webapp/pnpm-lock.yaml: Language not supported

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

Comment thread apps/customer-portal/webapp/src/api/useGetProjectSupportStats.ts
Comment thread apps/customer-portal/webapp/src/api/useGetProjects.ts
Comment thread apps/customer-portal/webapp/src/api/useGetProjectSupportStats.ts
Comment thread apps/customer-portal/webapp/src/models/responses.ts
Comment thread apps/customer-portal/webapp/src/pages/SupportPage.tsx
Comment thread apps/customer-portal/webapp/src/pages/SupportPage.tsx
@Rashmika998
Rashmika998 merged commit b610cdc into wso2-open-operations:customer-portal-milestone-1 Feb 4, 2026
1 check passed
@Rashmika998 Rashmika998 moved this from Done to Staging Deployed in Customer Portal Development Feb 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Staging Deployed

Development

Successfully merging this pull request may close these issues.

3 participants