Skip to content

[Customer Portal][FE][Web] Implement Project Details Page and Modular Overview Components - #88

Merged
Rashmika998 merged 78 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/add-project-overview-page
Feb 4, 2026
Merged

Rashmika998 merged 78 commits into
wso2-open-operations:customer-portal-milestone-1from
dileepapeiris:feat/add-project-overview-page

Conversation

@dileepapeiris

@dileepapeiris dileepapeiris commented Feb 3, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

This PR introduces the Project Details page, providing a comprehensive 360-degree view of a specific project. It establishes a tabbed navigation structure (Overview, Deployments, Time Tracking) and implements a data-rich Overview tab composed of modular cards for project metadata, health statistics, contact information, and recent activity.

Screen Recordings and Screenshots

Screen.Recording.2026-02-03.at.17.08.24.mp4

Project Overview Page

** Light Mode**
image

image

Dark Mode
image

image

###Loading Modal

** Light Mode**
image

image

Dark Mode
image

image

Goals

  • Create a centralized hub for project-specific information and management.
  • Implement a reusable TabBar component for consistent sub-navigation across different project modules.
  • Develop a modular card-based architecture for the Overview tab to ensure high maintainability and readability.
  • Integrate project-specific API hooks to fetch metadata and performance statistics.
  • Achieve full test coverage for the extensive suite of new UI components.

Approach

  • Tabbed Architecture: Implemented the ProjectDetails page as a layout orchestrator using a shared TabBar component to switch between functional views.
  • Modular Design: The Overview tab is broken down into four primary functional areas:
    • Project Information: Handles branding, descriptions, and metadata.
    • Project Statistics: Visualizes KPIs like Open Cases and Active Chats using status-driven cards.
    • Contact Information: Displays key project stakeholders with easy access to contact details.
    • Recent Activity: Provides a chronological audit trail of project events.
  • Data Fetching: Utilized specialized hooks (useGetProjectDetails, useGetProjectStat) to decouple UI components from data fetching logic.
  • Consistency: Centralized tab definitions and labels in projectDetailsConstants.tsx for easy configuration.

User stories

  • Project Insight: As a user, I want to see a summary of my project's configuration, metadata, and subscription status in one place.
  • Health Monitoring: As a user, I want to see real-time statistics on open cases and deployments to assess my project's health.
  • Stakeholder Access: As a user, I want to quickly find and contact the Technical Owner or CSM assigned to my project.
  • Activity Tracking: As a user, I want to see a list of recent activities so I can stay updated on changes within the project.

Walkthrough

This PR builds the Project Details ecosystem: the main page container, a reusable sub-navigation tab bar, and four major card components (Information, Stats, Contacts, and Activity) with their respective sub-components. It also includes the necessary API hooks and constants to power the view.

Changes

Cohort File(s) Summary
Pages ProjectDetails.tsx Main page orchestrating the tabbed view and data fetching.
Shared UI TabBar.tsx Reusable sub-navigation component for switching project views.
Information Card ProjectInformationCard.tsx, ProjectHeader.tsx, ProjectMetadata.tsx, etc. Suite of components for project name, description, and metadata.
Statistics Card ProjectStatisticsCard.tsx UI for visualizing KPIs like cases, chats, and deployments.
Contact Card ContactInfoCard.tsx, ContactRow.tsx Components to display project stakeholders and their contact info.
Activity Card RecentActivityCard.tsx Timeline-based view for project-related events.
API Hooks useGetProjectDetails.ts, useGetProjectStat.ts Data fetching hooks with integrated error/loading handling.
Constants projectDetailsConstants.tsx Tab definitions and configuration labels.

Automation tests

  • Component Testing: Every UI component (10+) includes a dedicated Vitest file covering rendering, prop passing, and conditional states (e.g., SLA badges, support tiers).
  • Hook Testing: Verified useGetProjectDetails and useGetProjectStat for successful data resolution, empty states, and API errors.
  • Integration Testing: Verified the TabBar correctly switches between component views within the ProjectDetails page.
image

Summary by CodeRabbit

  • New Features

    • Full app routing and shell with a global loader; project-scoped routes (Dashboard, Project Details, Create Case, AI chat).
    • Case creation flow and UI sections, chat interface with message bubbles, escalation CTA, and conversation sidebar.
    • New UI widgets: tab bar, active filters, cases listing with header/filters, project info/stat cards, contact list, subscription/status displays.
  • Tests

    • Extensive unit and integration tests covering components, pages, hooks, and utilities.

Add comprehensive unit tests for the TabBar component (apps/customer-portal/webapp/src/components/common/tabBar/__tests__/TabBar.test.tsx). Tests use Vitest and React Testing Library, mock @wso2/oxygen-ui and a sample icon, and cover rendering, ARIA roles/aria-selected, icons, count badges (including string/zero/custom color), click interaction (onTabChange), combined feature scenarios, and edge cases (single/empty/long labels and nonexistent activeTab).
Introduce a new TabBar React component that renders a styled tab list using @wso2/oxygen-ui Card, Button and Box. Defines TabOption and TabBarProps interfaces, supports icons, count badges, active state styling, accessible roles (tablist/tab) and onTabChange callbacks. Exports the component as default.
Introduce PROJECT_DETAILS_TABS constant with three TabOption entries (overview, deployments, time-tracking) including associated icons (Info, Server, Clock). Adds a typed constants file at apps/customer-portal/webapp/src/constants/projectDetailsConstants.ts and includes the project license header.
Replace usage of ProjectPage (with title prop) by the new ProjectDetails component for the "project-details" route in App.tsx. Added import for ProjectDetails and updated the Route element to render it directly.
Introduce ProjectDetails React component that provides a tabbed project page layout. It uses PROJECT_DETAILS_TABS and a TabBar component, manages activeTab state (defaulting to "overview") and renders placeholder content for "overview", "deployments", and "time-tracking". Uses WSO2 oxygen-ui Box and Typography for layout and styling. (Note: useParams is imported but not yet used.)
@dileepapeiris dileepapeiris self-assigned this Feb 3, 2026
@dileepapeiris
dileepapeiris requested a review from Copilot February 3, 2026 01:28
@coderabbitai

coderabbitai Bot commented Feb 3, 2026 •

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

Replaces the placeholder App with a Router-backed shell: adds LoaderProvider, AppLayout, nested project routes and pages (dashboard, project details, chat, create-case), many new UI components, React Query hooks, mock data/generators, utils, and extensive unit tests.

Changes

Cohort / File(s) Summary
App foundation & routing
apps/customer-portal/webapp/src/App.tsx, apps/customer-portal/webapp/src/layouts/AppLayout.tsx, apps/customer-portal/webapp/src/context/linearLoader/LoaderContext.tsx
Implements Router-based App, registers nested project routes, wraps routes with LoaderProvider, and adds an app shell with a ref-counted linear loader and useLoader hook.
Pages
apps/customer-portal/webapp/src/pages/DashboardPage.tsx, apps/customer-portal/webapp/src/pages/ProjectDetails.tsx, apps/customer-portal/webapp/src/pages/CreateCasePage.tsx, apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx
Adds Dashboard, ProjectDetails, CreateCase, and Novera chat pages; each uses React Query hooks, loader/logging, navigation, and composes new components.
Support / Case creation & chat components
apps/customer-portal/webapp/src/components/support/caseCreationLayout/*, apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/*
New case-creation layout fragments (header, basic info, details) and chat UI (message bubble/list, escalation banner, header/input).
Cases & dashboard UI
apps/customer-portal/webapp/src/components/dashboard/casesTable/*, apps/customer-portal/webapp/src/components/common/tabBar/TabBar.tsx
Adds CasesTable and CasesTableHeader with filtering/pagination, and a reusable TabBar component for tabbed views.
Filter UI
apps/customer-portal/webapp/src/components/common/filterPanel/ActiveFilters.tsx
Adds generic ActiveFilters component rendering chips and optional option menus with update/remove/clear handlers.
Project details UI, constants & utils
apps/customer-portal/webapp/src/components/projectDetails/..., apps/customer-portal/webapp/src/constants/projectDetailsConstants.tsx, apps/customer-portal/webapp/src/utils/projectStats.ts
Adds project overview/statistics/contact/recent-activity components, constants for tabs/contacts/stats, and utilities for date formatting, color mapping, subscription status, and progress calculation.
API hooks & constants
apps/customer-portal/webapp/src/api/useGetProjectDetails.ts, apps/customer-portal/webapp/src/api/useGetProjectStat.ts, apps/customer-portal/webapp/src/constants/apiConstants.ts
New React Query hooks using mock data with configurable API_MOCK_DELAY and query keys; include logging and error behavior.
Models, mocks & generators
apps/customer-portal/webapp/src/models/mockData.ts, apps/customer-portal/webapp/src/models/mockFunctions.ts, apps/customer-portal/webapp/src/models/responses.ts
Adds typed mock datasets, case-creation metadata, mock generators for dashboard/cases, and expanded response interfaces (ProjectDetails, ProjectStatsResponse).
Tests
apps/customer-portal/webapp/src/**/__tests__/*.test.tsx, apps/customer-portal/webapp/src/utils/__tests__/projectStats.test.ts
Large suite of unit tests for components, hooks, and utils; extensive mocking of UI libs, icons, routing, and time-dependent logic.
Small API change
apps/customer-portal/webapp/src/components/common/header/Header.tsx
Removes collapsed prop from HeaderProps and updates Header signature to only accept onToggleSidebar.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant RouterApp as "App (Router)"
  participant LoaderProv as "LoaderProvider"
  participant AppShell as "AppLayout"
  participant Page as "DashboardPage"
  participant Hook as "useGetDashboardMockStats"
  participant MockData as "mockFunctions"

  Client->>RouterApp: navigate to "/:projectId/dashboard"
  RouterApp->>LoaderProv: wrap routes
  LoaderProv->>AppShell: render shell (header/sidebar/outlet)
  AppShell->>Page: mount DashboardPage via Outlet
  Page->>LoaderProv: showLoader()
  Page->>Hook: fetch dashboard stats (projectId)
  Hook->>MockData: generate/read mock stats (API_MOCK_DELAY)
  MockData-->>Hook: return stats
  Hook-->>Page: data received
  Page->>LoaderProv: hideLoader()
  Page->>AppShell: render charts and CasesTable
  Client->>Page: click "Get Support"
  Page->>RouterApp: navigate to "/:projectId/support"
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • v15a1
  • Rashmika998

"I hop and code with nimble feet,
Routes unfurl and loaders beat,
Chats reply and cases bloom,
Tabs and cards now fill the room,
A rabbit cheers — features complete!" 🐇✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title '[Customer Portal][FE][Web] Implement Project Details Page and Modular Overview Components' is specific and directly summarizes the main change: implementing a Project Details page with modular overview components.
Description check ✅ Passed The PR description is comprehensive and follows most of the template structure with Purpose, Goals, Approach, User stories, Changes table, Automation tests, and supporting screenshots. All critical sections are present and well-detailed.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% 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.

@dileepapeiris dileepapeiris added Type/New Feature Represents a request or task for a new feature Type/Task General task that does not fit into other categories Type/UX Refers to user experience-related tasks or issues App/Customer Portal Area/Frontend Platform/Web labels Feb 3, 2026

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 PR builds out the initial customer portal UI for project-level navigation and support, including a shared layout, routing, dashboard, and support experiences, all backed by mock data and React Query hooks.

Changes:

  • Introduces a top-level App router with an AppLayout using Oxygen UI’s AppShell, global notification banner, linear loader context, header, sidebar, and footer.
  • Adds project-level pages and components (Project Hub cards, Dashboard with stats/charts/cases table, Support page with support stats and Novera AI chat entry points, and case-creation layout), along with supporting models, constants, and mock-driven API hooks.
  • Expands configuration and utilities (auth, API, logger, theme, notification banner, project/case utilities) and adds extensive Vitest coverage for the new components, hooks, and contexts, plus test-focused Vitest/vite tuning.

Reviewed changes

Copilot reviewed 138 out of 139 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
apps/customer-portal/webapp/vite.config.ts Updates Vitest config to inline Oxygen UI/chart dependencies and enable CSS for tests.
apps/customer-portal/webapp/src/utils/projectCard.ts Adds helpers to format project dates and map project “health” statuses to theme color tokens.
apps/customer-portal/webapp/src/utils/logger.ts Minor comment style clean-up for the logger implementation.
apps/customer-portal/webapp/src/utils/casesTable.ts Adds color-mapping utilities for case priority and status used in the dashboard cases table.
apps/customer-portal/webapp/src/utils/tests/projectCard.test.ts Unit tests for formatProjectDate and project status color mapping.
apps/customer-portal/webapp/src/utils/tests/logger.test.ts Unit tests for logger level filtering, formatting, and log level parsing.
apps/customer-portal/webapp/src/utils/tests/casesTable.test.ts Unit tests for priority and status color helpers used by the cases table.
apps/customer-portal/webapp/src/pages/SupportPage.tsx New support page that loads project support stats, logs load/error states, renders stat cards and the Novera chat banner, and shows an error message on failure.
apps/customer-portal/webapp/src/pages/ProjectPage.tsx Simple generic project sub-page component that displays a title and project ID.
apps/customer-portal/webapp/src/pages/ProjectDetails.tsx Adds a project details page with a tabbed layout (overview/deployments/time-tracking) using the shared TabBar.
apps/customer-portal/webapp/src/models/responses.ts Defines typed response models for projects, user profile, support/case stats, trend data, dashboard stats, and case lists.
apps/customer-portal/webapp/src/models/requests.ts Defines typed request models for project search and case search (filters, pagination, sorting).
apps/customer-portal/webapp/src/layouts/AppLayout.tsx New application layout using Oxygen UI AppShell, global notification banner, sidebar, header, footer, and linear loader integration around routed content.
apps/customer-portal/webapp/src/hooks/useLogger.ts Simplifies logger hook documentation while keeping error guard when context is missing.
apps/customer-portal/webapp/src/context/logger/tests/LoggerContext.test.tsx Updates copyright header style in logger context tests.
apps/customer-portal/webapp/src/context/logger/LoggerProvider.tsx Removes redundant comment noise around logger provider props.
apps/customer-portal/webapp/src/context/logger/LoggerContext.tsx Extends JSDoc on logger context with explicit type annotation in the comment.
apps/customer-portal/webapp/src/context/linearLoader/tests/LoaderContext.test.tsx Adds tests verifying loader visibility toggling, error when used outside provider, and child rendering.
apps/customer-portal/webapp/src/context/linearLoader/LoaderContext.tsx Implements a simple context/provider and hook for a global linear progress loader.
apps/customer-portal/webapp/src/constants/supportConstants.ts Configures support stat cards (icons, colors, labels, keys) for the support page metrics.
apps/customer-portal/webapp/src/constants/projectDetailsConstants.ts Defines tab metadata (ids, labels, icons) for the project details page.
apps/customer-portal/webapp/src/constants/appLayoutConstants.ts Introduces sidebar navigation items and layout-related constants (URLs, company name).
apps/customer-portal/webapp/src/constants/apiConstants.ts Adds a global mock delay and centralizes React Query key constants.
apps/customer-portal/webapp/src/config/themeConfig.ts Converts theme configuration comments to line comments; retains theme selection based on env.
apps/customer-portal/webapp/src/config/notificationBannerConfig.ts Adds typed config for a global notification banner sourced from environment variables.
apps/customer-portal/webapp/src/config/loggerConfig.ts Minor comment style update for logger configuration.
apps/customer-portal/webapp/src/config/authConfig.ts Minor comment style update for Asgardeo auth configuration.
apps/customer-portal/webapp/src/config/apiConfig.ts Minor comment style updates for API base URL and config; preserves runtime env validation.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/tests/EscalationBanner.test.tsx Tests EscalationBanner rendering, visibility toggling, and callback invocation.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/tests/ChatMessageList.test.tsx Tests that the message list renders provided messages.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/tests/ChatMessageBubble.test.tsx Tests user vs bot message rendering and presence of bot avatar icon.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/tests/ChatInput.test.tsx Tests chat input behavior, send button, enter key, and escalation banner interactions.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/tests/ChatHeader.test.tsx Tests that the chat header back button calls the provided callback.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/EscalationBanner.tsx Implements an inline escalation banner prompting case creation from a chat.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/ChatMessageList.tsx Renders a scrollable list of chat message bubbles with an end-of-list ref.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/ChatMessageBubble.tsx Renders a single message bubble with different layout and avatar for user vs bot.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/ChatInput.tsx Renders the chat input field, send button, and optional escalation banner with enter-key handling.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/ChatHeader.tsx Renders the Novera chat header with back navigation and assistant branding.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatBanner/tests/NoveraChatBanner.test.tsx Tests banner rendering and navigation to the chat route on button click.
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatBanner/NoveraChatBanner.tsx “Start New Chat” banner to route users into the Novera AI chat flow.
apps/customer-portal/webapp/src/components/support/casesOverviewStats/tests/CasesOverviewStatCard.test.tsx Tests loading vs loaded rendering for support stat cards, including icons and labels.
apps/customer-portal/webapp/src/components/support/casesOverviewStats/CasesOverviewStatCard.tsx Renders a grid of summary StatCards for project support stats with optional skeletons.
apps/customer-portal/webapp/src/components/support/caseCreationLayout/tests/ConversationSummary.test.tsx Tests conversation summary sidebar behavior for loading, loaded, and missing metadata.
apps/customer-portal/webapp/src/components/support/caseCreationLayout/tests/CaseCreationHeader.test.tsx Tests header titles, labels, and back button behavior on the case creation page.
apps/customer-portal/webapp/src/components/support/caseCreationLayout/tests/AIInfoCard.test.tsx Tests the informational AI auto-population card content and icon.
apps/customer-portal/webapp/src/components/support/caseCreationLayout/ConversationSummary.tsx Sidebar summarizing conversation metrics with skeleton loading and an informational tip.
apps/customer-portal/webapp/src/components/support/caseCreationLayout/CaseCreationHeader.tsx Header section for reviewing AI-populated case details, with back navigation and “AI Generated” chip.
apps/customer-portal/webapp/src/components/support/caseCreationLayout/AIInfoCard.tsx Small explanatory card indicating fields were auto-populated from the Novera conversation.
apps/customer-portal/webapp/src/components/projectHub/projectCard/tests/ProjectCardStats.test.tsx Tests the stats section of a project card (counts, date formatting, icons).
apps/customer-portal/webapp/src/components/projectHub/projectCard/tests/ProjectCardSkeleton.test.tsx Tests that the project card skeleton renders expected structural placeholders.
apps/customer-portal/webapp/src/components/projectHub/projectCard/tests/ProjectCardInfo.test.tsx Tests project card info block title/subtitle rendering.
apps/customer-portal/webapp/src/components/projectHub/projectCard/tests/ProjectCardBadges.test.tsx Tests project card badges (key and status) and status color mapping.
apps/customer-portal/webapp/src/components/projectHub/projectCard/tests/ProjectCardActions.test.tsx Tests project card actions calling the view-dashboard callback and stopping propagation.
apps/customer-portal/webapp/src/components/projectHub/projectCard/tests/ProjectCard.test.tsx Integration-style tests for the full project card, navigation, and override callback.
apps/customer-portal/webapp/src/components/projectHub/projectCard/ProjectCardStats.tsx Stats subcomponent for a project card showing open cases, active chats, and created date.
apps/customer-portal/webapp/src/components/projectHub/projectCard/ProjectCardSkeleton.tsx Skeleton project card to show while project data is loading.
apps/customer-portal/webapp/src/components/projectHub/projectCard/ProjectCardInfo.tsx Subcomponent rendering project card title and truncated description.
apps/customer-portal/webapp/src/components/projectHub/projectCard/ProjectCardBadges.tsx Subcomponent rendering project key and status chips using getStatusColor.
apps/customer-portal/webapp/src/components/projectHub/projectCard/ProjectCardActions.tsx Subcomponent rendering the “View Dashboard” button and wiring its click handler.
apps/customer-portal/webapp/src/components/projectHub/projectCard/ProjectCard.tsx Full project card component combining badges, info, stats, and actions with mock fallbacks and navigation.
apps/customer-portal/webapp/src/components/dashboard/stats/tests/TrendIndicator.test.tsx Tests trend indicator rendering for loading, up, and down trends.
apps/customer-portal/webapp/src/components/dashboard/stats/TrendIndicator.tsx Trend pill component for showing up/down indicators and “vs last month” text.
apps/customer-portal/webapp/src/components/dashboard/stats/StatCard.tsx Custom dashboard stat card component with icon, trend indicator, value, and tooltip.
apps/customer-portal/webapp/src/components/dashboard/charts/tests/ChartLegend.test.tsx Tests that the chart legend renders items and colors correctly and handles empty data.
apps/customer-portal/webapp/src/components/dashboard/charts/tests/ChartLayout.test.tsx Tests that layout renders all chart placeholders and passes loading state.
apps/customer-portal/webapp/src/components/dashboard/charts/tests/CasesTrendChart.test.tsx Tests that the cases trend chart renders its title, skeleton state, chart, and legend.
apps/customer-portal/webapp/src/components/dashboard/charts/ChartLegend.tsx Reusable legend component for charts showing colored dots and labels.
apps/customer-portal/webapp/src/components/dashboard/charts/ChartLayout.tsx Layout that arranges outstanding incidents, active cases, and cases trend charts.
apps/customer-portal/webapp/src/components/dashboard/charts/CasesTrendChart.tsx Stacked bar chart of case trends over time with legend and loading skeleton.
apps/customer-portal/webapp/src/components/dashboard/charts/ActiveCasesChart.tsx Donut chart for distribution of active cases plus total center label and legend.
apps/customer-portal/webapp/src/components/dashboard/casesTable/tests/CasesTableHeader.test.tsx Tests header copy, button callbacks, and active filter rendering for the cases table.
apps/customer-portal/webapp/src/components/dashboard/casesTable/CasesTableSkeleton.tsx Row skeletons for the cases table during data loading.
apps/customer-portal/webapp/src/components/dashboard/casesTable/CasesTableHeader.tsx Header for cases table with title, active filters display, and filter/create buttons.
apps/customer-portal/webapp/src/components/common/tabBar/TabBar.tsx Generic tab bar component implemented with Oxygen UI Card + Button.
apps/customer-portal/webapp/src/components/common/sideNavBar/tests/SubscriptionWidget.test.tsx Tests subscription widget visibility vs collapsed state and contents.
apps/customer-portal/webapp/src/components/common/sideNavBar/SubscriptionWidget.tsx Sidebar subscription widget with short text and “View Details” button.
apps/customer-portal/webapp/src/components/common/sideNavBar/SideBar.tsx Wrapper around Oxygen UI Sidebar to render navigation items, subscription widget, and settings link.
apps/customer-portal/webapp/src/components/common/notificationBanner/tests/GlobalNotificationBanner.test.tsx Tests global notification banner visibility sync and dismissal.
apps/customer-portal/webapp/src/components/common/notificationBanner/GlobalNotificationBanner.tsx Global notification banner component that respects config and supports dismissal.
apps/customer-portal/webapp/src/components/common/header/tests/UserProfile.test.tsx Tests that user profile renders mock user name and email via UserMenu.
apps/customer-portal/webapp/src/components/common/header/tests/SearchBar.test.tsx Tests that search bar renders with expected placeholder.
apps/customer-portal/webapp/src/components/common/header/tests/ProjectSwitcher.test.tsx Tests project switcher behavior including loading skeleton and change callback.
apps/customer-portal/webapp/src/components/common/header/tests/Brand.test.tsx Tests brand logo and “Customer Portal” title rendering.
apps/customer-portal/webapp/src/components/common/header/tests/Actions.test.tsx Tests header action area (join community link, theme toggle, and user profile).
apps/customer-portal/webapp/src/components/common/header/UserProfile.tsx Header user profile component wiring UserMenu to mock user and navigation/logger callbacks.
apps/customer-portal/webapp/src/components/common/header/SearchBar.tsx Wrapper around Oxygen UI SearchBar with fixed placeholder and sizing.
apps/customer-portal/webapp/src/components/common/header/ProjectSwitcher.tsx Header project switcher using ComplexSelect and a loading skeleton state.
apps/customer-portal/webapp/src/components/common/header/Brand.tsx Header brand block with WSO2 logo and product name.
apps/customer-portal/webapp/src/components/common/header/Actions.tsx Header action area with “Join our community” link, theme toggle, and user profile.
apps/customer-portal/webapp/src/components/common/footer/tests/Footer.test.tsx Tests footer links and company name wiring from constants.
apps/customer-portal/webapp/src/components/common/footer/Footer.tsx Footer wrapper passing company name and legal URLs to Oxygen UI Footer.
apps/customer-portal/webapp/src/api/useGetProjects.ts React Query infinite-query hook to load projects from mock data with paging and optional “fetch all” mode.
apps/customer-portal/webapp/src/api/useGetProjectSupportStats.ts React Query hook to load project support stats from mock functions with logging and mock delay.
apps/customer-portal/webapp/src/api/useGetProjectCasesStats.ts React Query hook to load per-project case statistics, with logging and mock delay.
apps/customer-portal/webapp/src/api/useGetProjectCases.ts React Query hook to search project cases using mock case data, filters, and pagination.
apps/customer-portal/webapp/src/api/useGetDashboardMockStats.ts React Query hook exposing mock dashboard stats (trends and trend-series) per project.
apps/customer-portal/webapp/src/api/useGetCaseCreationDetails.ts React Query hook to fetch mock case-creation metadata and conversation summary.
apps/customer-portal/webapp/src/api/tests/useGetProjects.test.tsx Tests paging, default pagination, and fetchAll behavior of useGetProjects.
apps/customer-portal/webapp/src/api/tests/useGetProjectSupportStats.test.tsx Tests success, logging, and disabled behavior for useGetProjectSupportStats.
apps/customer-portal/webapp/src/api/tests/useGetProjectCasesStats.test.tsx Tests success and disabled behavior for useGetProjectCasesStats.
apps/customer-portal/webapp/src/api/tests/useGetDashboardMockStats.test.tsx Tests success and disabled behavior for useGetDashboardMockStats.
apps/customer-portal/webapp/src/api/tests/useGetCaseCreationDetails.test.tsx Tests successful and failing flows for the case creation metadata hook.
apps/customer-portal/webapp/src/App.tsx Defines the top-level router, wraps the app with BrowserRouter and LoaderProvider, and wires all project and support routes into AppLayout.
apps/customer-portal/webapp/package.json Updates dependencies (adds React Router core, Asgardeo router, bumps Oxygen UI charts, and reverts vite to a standard version without pnpm override).
apps/customer-portal/webapp/.env.example Documents new environment variables for controlling the global maintenance banner.

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

Comment thread apps/customer-portal/webapp/src/App.tsx
Comment thread apps/customer-portal/webapp/src/config/notificationBannerConfig.ts
Comment thread apps/customer-portal/webapp/src/pages/ProjectDetails.tsx Outdated
Comment thread apps/customer-portal/webapp/src/pages/DashboardPage.tsx Outdated

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

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🤖 Fix all issues with AI agents
In
`@apps/customer-portal/webapp/src/components/support/casesOverviewStats/CasesOverviewStatCard.tsx`:
- Around line 74-82: The component imports and uses the wrong StatCard and
unsafe casting: in CasesOverviewStatCard replace the external StatCard import
from `@wso2/oxygen-ui` with the local StatCard from
"@/components/dashboard/stats/StatCard", remove the unsafe cast of Skeleton to
number, and pass the loading state directly via the StatCard's isLoading prop
(e.g., value={stats?.[stat.key] ?? 0} and isLoading={isLoading}) so the local
StatCard handles skeleton rendering internally; update any prop types
accordingly in the CasesOverviewStatCard component so no unknown/number cast is
needed.

In
`@apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/ChatMessageList.tsx`:
- Around line 21-26: Move the duplicated Message interface into the shared
models module and export it, then replace the local interface declarations with
an import of Message in each component: remove the Message declaration from
ChatMessageList.tsx, ChatMessageBubble.tsx, and NoveraChatPage.tsx and add a
single import { Message } from the shared models file; ensure the exported
interface keeps the same fields (id: string, text: string, sender: "user" |
"bot", timestamp: Date) and update any prop/type usages (e.g., props or state
typed as Message[]) to reference the imported Message type.

In `@apps/customer-portal/webapp/src/pages/CreateCasePage.tsx`:
- Around line 56-63: The effect in CreateCasePage re-runs because showLoader and
hideLoader are recreated each render; update LoaderProvider by importing
useCallback and wrap the exported functions (showLoader and hideLoader) with
useCallback (e.g., memoize setIsVisible handlers) so their references remain
stable, then the useEffect([isLoading, showLoader, hideLoader]) will no longer
trigger unnecessarily.

In `@apps/customer-portal/webapp/src/utils/projectCard.ts`:
- Around line 24-40: formatProjectDate is timezone‑sensitive because it uses
local getters; make the output deterministic by normalizing to UTC: when
creating the date (in formatProjectDate) keep parsing the ISO string but then
use UTC-based accessors (e.g., getUTCDate and getUTCFullYear) or format the
month via Intl.DateTimeFormat('en-US', { month: 'short', timeZone: 'UTC' }) on
the same Date instance (refer to the date variable) so the returned string
always represents the UTC date regardless of the user's timezone.
🟡 Minor comments (17)
apps/customer-portal/webapp/src/pages/ProjectPage.tsx-31-43 (1)

31-43: ⚠️ Potential issue | 🟡 Minor

Add runtime safety check for missing projectId for defensive robustness.

While the routing structure (/:projectId/*) ensures projectId is present when this component renders, React Router's useParams API types all params as string | undefined for safety. Adding a runtime check aligns with React Router best practices.

Instead of using a placeholder, prefer early return or error handling:

♻️ Recommended approach
 export default function ProjectPage({ title }: ProjectPageProps): JSX.Element {
   const { projectId } = useParams<{ projectId: string }>();
+  if (!projectId) return null; // or <ErrorBoundary /> / <NotFound />
 
   return (
     <Box>
       {/* project page title */}
       <Typography variant="h4" gutterBottom>
         {title}
       </Typography>
       {/* project page subtitle */}
       <Typography variant="body1" color="text.secondary">
-        Displaying content for Project: <strong>{projectId}</strong>
+        Displaying content for Project: <strong>{projectId}</strong>
       </Typography>
     </Box>
   );
 }

Alternatively, the suggested fallback value works but early return is clearer about handling the edge case explicitly.

apps/customer-portal/webapp/src/components/common/tabBar/TabBar.tsx-28-47 (1)

28-47: ⚠️ Potential issue | 🟡 Minor

Unused className prop in TabBarProps.

The className prop is declared in TabBarProps (line 32) but never destructured or applied to the component. This makes the public API misleading—consumers cannot style the tab bar container via this prop.

Proposed fix
-const TabBar = ({ tabs, activeTab, onTabChange }: TabBarProps): JSX.Element => {
+const TabBar = ({
+  tabs,
+  activeTab,
+  onTabChange,
+  className,
+}: TabBarProps): JSX.Element => {
   return (
     <Card
       role="tablist"
+      className={className}
       sx={{
apps/customer-portal/webapp/src/context/logger/LoggerContext.tsx-22-26 (1)

22-26: ⚠️ Potential issue | 🟡 Minor

Use @type instead of @types for JSDoc.
@types is not a standard JSDoc tag and will be ignored by tooling. The correct tag is @type.

🔧 Proposed fix
- * `@types` {Context<ILogger | null>} The LoggerContext.
+ * `@type` {Context<ILogger | null>} The LoggerContext.
apps/customer-portal/webapp/src/utils/__tests__/projectCard.test.ts-22-35 (1)

22-35: ⚠️ Potential issue | 🟡 Minor

Test expectation is timezone-dependent and will fail in western timezones.

The input "2026-01-29T11:28:40+05:30" represents 29 Jan 2026 11:28:40 IST. JavaScript parses this as UTC (05:58:40Z on 29 Jan), then getDate() and getFullYear() return values in the local machine timezone. In PST (UTC-8), this becomes 28 Jan 21:58:40, causing the test to expect day "28" instead of "29".

Either:

  • Use a fixed timeZone parameter in date formatting (e.g., date.toLocaleString("en-US", { month: "short", timeZone: "UTC" })) and align the test to that timezone, or
  • Mock the system timezone in tests to ensure consistent results across environments.
apps/customer-portal/webapp/src/utils/casesTable.ts-40-47 (1)

40-47: ⚠️ Potential issue | 🟡 Minor

Guard getStatusColor against empty/undefined labels.

label.toLowerCase() will throw if status is missing; mirror the defensive pattern used in getPriorityColor.

🛠️ Suggested fix
-export const getStatusColor = (label: string): string => {
-  const normalized = label.toLowerCase();
+export const getStatusColor = (label?: string): string => {
+  const normalized = label?.toLowerCase() || "";
   if (normalized.includes("open")) return "primary.main";
   if (normalized.includes("awaiting")) return "info.main";
   if (normalized.includes("progress")) return "warning.main";
   if (normalized.includes("resolved") || normalized.includes("closed"))
     return "success.main";
   return "text.secondary";
 };
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/ChatInput.tsx-52-63 (1)

52-63: ⚠️ Potential issue | 🟡 Minor

Prevent default on Enter regardless of input content.

This avoids accidental form submits if ChatInput is embedded in a <form>.

🛠️ Suggested fix
           onKeyDown={(e) => {
-            if (e.key === "Enter") {
-              if (!inputValue.trim()) return;
-              e.preventDefault();
-              onSend();
-            }
+            if (e.key !== "Enter") return;
+            e.preventDefault();
+            if (!inputValue.trim()) return;
+            onSend();
           }}
apps/customer-portal/webapp/src/constants/dashboardConstants.ts-159-164 (1)

159-164: ⚠️ Potential issue | 🟡 Minor

Inconsistent casing in deployment option values.

"Development" uses PascalCase while "production" uses lowercase. This inconsistency may cause issues with filtering if backend expects consistent casing.

🔧 Suggested fix
   options: [
     { label: "Development", value: "Development" },
-    { label: "Production", value: "production" },
+    { label: "Production", value: "Production" },
     { label: "QA", value: "QA" },
     { label: "Staging", value: "Staging" },
   ],
apps/customer-portal/webapp/src/components/common/filterPanel/ActiveFilters.tsx-46-106 (1)

46-106: ⚠️ Potential issue | 🟡 Minor

Handle falsy-but-valid filter values consistently.
Boolean(value) and value || field.label treat 0/false as inactive, which can hide valid filters and block deletion.

🛠️ Suggested fix
-  const activeFiltersCount =
-    Object.values(appliedFilters).filter(Boolean).length;
+  const hasFilterValue = (value: unknown): boolean => {
+    if (Array.isArray(value)) {
+      return value.length > 0;
+    }
+    return value !== undefined && value !== null && value !== "";
+  };
+
+  const activeFiltersCount =
+    Object.values(appliedFilters).filter(hasFilterValue).length;
@@
-        const isActive = Boolean(value);
+        const isActive = hasFilterValue(value);
+        const displayLabel = isActive ? String(value) : field.label;
@@
-              label={value || field.label}
+              label={displayLabel}
apps/customer-portal/webapp/src/components/dashboard/charts/ChartLayout.tsx-50-55 (1)

50-55: ⚠️ Potential issue | 🟡 Minor

Fix JSDoc param types to match the actual prop shapes.
The current @param entries say number for object props, which is misleading.

📝 Suggested doc fix
- * `@param` {number} props.outstandingIncidents - Number of outstanding incidents.
- * `@param` {number} props.activeCases - Number of active cases.
- * `@param` {CasesTrendData[]} props.casesTrend - Array of trend data for cases.
+ * `@param` {Object} props.outstandingIncidents - Breakdown of outstanding incidents.
+ * `@param` {Object} props.activeCases - Breakdown of active cases.
+ * `@param` {Array<{ name: string; TypeA: number; TypeB: number; TypeC: number; TypeD: number }>} props.casesTrend - Trend data for cases.
apps/customer-portal/webapp/src/pages/__tests__/CreateCasePage.test.tsx-125-197 (1)

125-197: ⚠️ Potential issue | 🟡 Minor

Reset mocked hook return values per test to avoid order coupling.
The error-case override can leak into other tests if execution order changes. Add a default mock return in beforeEach.

Suggested fix
+const defaultCaseCreationDetails = {
+  projects: ["Production Environment-Main"],
+  products: ["WSO2 API Manager - v4.2.0"],
+  deploymentTypes: ["Production"],
+  issueTypes: ["Partial Outage"],
+  severityLevels: [
+    { id: "S1", label: "S1", description: "Desc 1" },
+    { id: "S2", label: "S2", description: "Desc 2" },
+  ],
+  conversationSummary: {
+    messagesExchanged: 8,
+    troubleshootingAttempts: "2 steps completed",
+    kbArticlesReviewed: "3 articles suggested",
+  },
+};
+
 vi.mock("@/api/useGetCaseCreationDetails", () => ({
   useGetCaseCreationDetails: vi.fn(() => ({
-    data: {
-      projects: ["Production Environment-Main"],
-      products: ["WSO2 API Manager - v4.2.0"],
-      deploymentTypes: ["Production"],
-      issueTypes: ["Partial Outage"],
-      severityLevels: [
-        { id: "S1", label: "S1", description: "Desc 1" },
-        { id: "S2", label: "S2", description: "Desc 2" },
-      ],
-      conversationSummary: {
-        messagesExchanged: 8,
-        troubleshootingAttempts: "2 steps completed",
-        kbArticlesReviewed: "3 articles suggested",
-      },
-    },
-    isLoading: false,
+    data: defaultCaseCreationDetails,
+    isLoading: false,
+    isError: false,
   })),
 }));
 
 describe("CreateCasePage", () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
+    vi.mocked(useGetCaseCreationDetails).mockReturnValue({
+      data: defaultCaseCreationDetails,
+      isLoading: false,
+      isError: false,
+    } as any);
+  });
apps/customer-portal/webapp/src/components/dashboard/stats/TrendIndicator.tsx-62-73 (1)

62-73: ⚠️ Potential issue | 🟡 Minor

Trend color ignores non-success values.

If trend.color is "info" or "warning", the current logic still renders text.secondary, so the UI won’t reflect the provided color.

🎨 Suggested fix to honor all allowed colors
-                color:
-                  trend.direction === "down"
-                    ? "error.main"
-                    : trend.color === "success"
-                      ? "success.main"
-                      : "text.secondary",
+                color:
+                  trend.direction === "down"
+                    ? "error.main"
+                    : `${trend.color}.main`,
apps/customer-portal/webapp/src/components/support/caseCreationLayout/ConversationSummary.tsx-96-110 (1)

96-110: ⚠️ Potential issue | 🟡 Minor

Interactive element missing click handler.

The "View full conversation" text is styled as a clickable link (cursor: pointer, hover underline) but lacks an onClick handler. This could confuse users who expect it to be functional.

Consider adding a handler or TODO
       <Typography
         variant="body2"
+        onClick={isLoading ? undefined : () => { /* TODO: implement */ }}
         sx={{
           color: "primary.main",
           cursor: isLoading ? "default" : "pointer",

Or use a Button component with variant="text" for better semantics if this will be interactive.

apps/customer-portal/webapp/src/pages/NoveraChatPage.tsx-95-106 (1)

95-106: ⚠️ Potential issue | 🟡 Minor

Potential message ID collision with rapid sends.

Using Date.now() + 1 for bot message IDs could collide if the user sends messages faster than 1ms apart. Consider using a more robust ID generation approach.

Proposed fix using crypto.randomUUID
     const timeoutId = window.setTimeout(() => {
       const botMessage: Message = {
-        id: (Date.now() + 1).toString(),
+        id: crypto.randomUUID(),
         text: getNoveraResponse(),
         sender: "bot",
         timestamp: new Date(),
       };
apps/customer-portal/webapp/src/pages/CreateCasePage.tsx-71-71 (1)

71-71: ⚠️ Potential issue | 🟡 Minor

Hardcoded array index for severity selection is fragile.

Using metadata.severityLevels?.[1]?.id assumes the second severity level is always the appropriate default. If the array order changes or has fewer than 2 elements, this could select an unexpected value or undefined.

Consider selecting by a known ID or using a default constant.

Suggested approach
-      setSeverity(metadata.severityLevels?.[1]?.id || "");
+      // Select a sensible default severity by ID, or fall back to first available
+      const defaultSeverity = metadata.severityLevels?.find(
+        (level) => level.id === "S2"
+      )?.id ?? metadata.severityLevels?.[0]?.id ?? "";
+      setSeverity(defaultSeverity);
apps/customer-portal/webapp/src/components/common/sideNavBar/SideBar.tsx-63-76 (1)

63-76: ⚠️ Potential issue | 🟡 Minor

Handle undefined projectId in link construction.

When projectId is undefined, the link will be /undefined/${item.path}, which leads to broken navigation. Consider guarding against this or providing a fallback.

🛡️ Proposed fix
  {APP_SHELL_NAV_ITEMS.map((item) => (
    <Link
      key={item.id}
      component={NavigateLink}
-     to={`/${projectId}/${item.path}`}
+     to={projectId ? `/${projectId}/${item.path}` : `/${item.path}`}
      color="inherit"
      underline="none"
    >

Alternatively, if navigation should be disabled without a project context, consider conditionally rendering or disabling the links.

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

85-97: ⚠️ Potential issue | 🟡 Minor

Same undefined projectId issue in Settings link.

The Settings link has the same potential issue with undefined projectId.

🛡️ Proposed fix
  <Link
    component={NavigateLink}
-   to={`/${projectId}/settings`}
+   to={projectId ? `/${projectId}/settings` : "/settings"}
    color="inherit"
    underline="none"
  >
apps/customer-portal/webapp/src/components/common/header/Header.tsx-128-133 (1)

128-133: ⚠️ Potential issue | 🟡 Minor

The collapsed prop is not being passed to HeaderUI.Toggle.

The component receives a collapsed prop (line 41) but line 132 hardcodes collapsed={false}. This means the sidebar toggle won't reflect the actual collapsed state.

🐛 Proposed fix
     <HeaderUI>
       {!isProjectHub && (
         /* header sidebar toggle */
-        <HeaderUI.Toggle collapsed={false} onToggle={onToggleSidebar} />
+        <HeaderUI.Toggle collapsed={collapsed} onToggle={onToggleSidebar} />
       )}
🧹 Nitpick comments (49)
apps/customer-portal/webapp/src/pages/ProjectDetails.tsx (2)

28-30: Derive initial tab from constants to avoid drift.

Line 29 hard-codes "overview"; this can get out of sync if tab IDs change. Prefer bootstrapping from PROJECT_DETAILS_TABS.

♻️ Proposed fix
-export default function ProjectDetails(): JSX.Element {
-  const [activeTab, setActiveTab] = useState<string>("overview");
+export default function ProjectDetails(): JSX.Element {
+  const [activeTab, setActiveTab] = useState<string>(
+    PROJECT_DETAILS_TABS[0]?.id ?? "overview",
+  );

31-59: Avoid rendering nothing on unexpected tab IDs.

Line 58 returns null, which yields a blank content area if activeTab is invalid (e.g., deep link or future tab changes). Consider a small fallback.

🛠️ Suggested fallback
       case "time-tracking":
         return (
           <Box sx={{ p: 3, textAlign: "center" }}>
             <Typography variant="h6" color="text.secondary">
               Time Tracking (Coming Soon)
             </Typography>
           </Box>
         );
       default:
-        return null;
+        return (
+          <Box sx={{ p: 3, textAlign: "center" }}>
+            <Typography variant="h6" color="text.secondary">
+              Coming Soon
+            </Typography>
+          </Box>
+        );
     }
   };
apps/customer-portal/webapp/src/components/common/tabBar/__tests__/TabBar.test.tsx (1)

108-113: Test doesn’t verify the custom className.

The test name says it checks className, but it only asserts the tablist exists. Consider asserting the class itself (especially if TabBar forwards it).

🧪 Suggested assertion
-      expect(screen.getByRole("tablist")).toBeInTheDocument();
+      const tablist = screen.getByRole("tablist");
+      expect(tablist).toBeInTheDocument();
+      expect(tablist).toHaveClass("custom-class");
apps/customer-portal/webapp/src/context/linearLoader/LoaderContext.tsx (1)

33-45: Consider reference-counting to avoid premature hides.
If multiple operations call showLoader/hideLoader, a boolean can hide while another is still in flight. A simple counter avoids that.

♻️ Suggested reference-counted loader
-  const [isVisible, setIsVisible] = useState(false);
-
-  const showLoader = () => setIsVisible(true);
-  const hideLoader = () => setIsVisible(false);
+  const [visibleCount, setVisibleCount] = useState(0);
+  const isVisible = visibleCount > 0;
+
+  const showLoader = () => setVisibleCount((c) => c + 1);
+  const hideLoader = () => setVisibleCount((c) => Math.max(0, c - 1));
apps/customer-portal/webapp/src/config/notificationBannerConfig.ts (1)

27-35: Validate env-provided severity to avoid unsupported values.

import.meta.env is runtime data; an unexpected string will bypass the union and could break UI styling. Consider normalizing to the allowed set with a safe fallback.

♻️ Suggested normalization
-export const notificationBannerConfig: NotificationBannerConfig = {
+const rawSeverity =
+  import.meta.env.CUSTOMER_PORTAL_MAINTENANCE_BANNER_SEVERITY || "info";
+const severity: NotificationBannerConfig["severity"] =
+  rawSeverity === "info" ||
+  rawSeverity === "warning" ||
+  rawSeverity === "error" ||
+  rawSeverity === "success"
+    ? rawSeverity
+    : "info";
+
+export const notificationBannerConfig: NotificationBannerConfig = {
   actionLabel: import.meta.env.CUSTOMER_PORTAL_MAINTENANCE_BANNER_ACTION_LABEL,
   actionUrl: import.meta.env.CUSTOMER_PORTAL_MAINTENANCE_BANNER_ACTION_URL,
   message: import.meta.env.CUSTOMER_PORTAL_MAINTENANCE_BANNER_MESSAGE || "",
-  severity:
-    import.meta.env.CUSTOMER_PORTAL_MAINTENANCE_BANNER_SEVERITY || "info",
+  severity,
   title: import.meta.env.CUSTOMER_PORTAL_MAINTENANCE_BANNER_TITLE || "",
   visible:
     import.meta.env.CUSTOMER_PORTAL_MAINTENANCE_BANNER_VISIBLE === "true",
 };
apps/customer-portal/webapp/src/components/support/caseCreationLayout/CaseDetailsSection.tsx (1)

33-44: Type metadata to avoid any leakage.

Tightening the metadata shape prevents silent runtime issues and improves IDE help.

♻️ Suggested typing
-interface CaseDetailsSectionProps {
+interface CaseCreationMetadata {
+  issueTypes: string[];
+  severityLevels: Array<{
+    id: string;
+    label: string;
+    description: string;
+  }>;
+}
+
+interface CaseDetailsSectionProps {
   title: string;
   setTitle: (value: string) => void;
   description: string;
   setDescription: (value: string) => void;
   issueType: string;
   setIssueType: (value: string) => void;
   severity: string;
   setSeverity: (value: string) => void;
-  metadata: any;
+  metadata?: CaseCreationMetadata;
   isLoading: boolean;
 }
apps/customer-portal/webapp/src/components/common/filterPanel/FilterPopover.tsx (2)

85-91: Avoid as any type assertion in handleReset.

The reset logic uses as any which bypasses type safety. Consider using a properly typed approach.

♻️ Suggested improvement
 const handleReset = () => {
-  const resetState = fields.reduce((acc, field) => {
-    acc[field.id] = "";
-    return acc;
-  }, {} as any);
+  const resetState = fields.reduce(
+    (acc, field) => {
+      acc[field.id as keyof T] = "" as T[keyof T];
+      return acc;
+    },
+    {} as T,
+  );
   setTempFilters(resetState);
 };

123-125: Duplicate comment.

Line 124 duplicates the comment on line 123.

🧹 Remove duplicate comment
         <Box sx={{ display: "flex", flexDirection: "column", gap: 3, pt: 2 }}>
           {/* filter popover fields */}
-          {/* filter popover fields */}
           {fields.map((field) =>
apps/customer-portal/webapp/src/constants/dashboardConstants.ts (1)

40-40: Consider stronger typing for icon property.

Using any for the icon type loses type safety. Consider using the icon component type from the library.

♻️ Suggested improvement
+import type { ComponentType, SVGProps } from "react";
+
 export interface StatConfigItem {
   id: Exclude<keyof DashboardMockStats, "casesTrend">;
   label: string;
-  icon: any;
+  icon: ComponentType<SVGProps<SVGSVGElement>>;
   iconColor: StatCardColor;
   tooltipText: string;
 }
apps/customer-portal/webapp/src/components/dashboard/charts/CasesTrendChart.tsx (1)

98-104: Hardcoded value: 0 in legend data is a workaround.

The ChartLegend component expects a value property, but it's not used for display in the legend. Passing value: 0 works but is semantically misleading. Consider making value optional in ChartLegendProps if it's not always needed.

This would require updating ChartLegend.tsx:

interface ChartLegendProps {
  data: Array<{ name: string; value?: number; color: string }>;
}
apps/customer-portal/webapp/src/components/common/filterPanel/__tests__/FilterPopover.test.tsx (1)

258-275: Reset test verifies UI state but doesn't verify callback behavior.

The reset test only checks that the UI value changes to empty string. Consider also verifying whether onSearch or onClose should be called (or explicitly not called) after reset, depending on intended behavior.

apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatBanner/__tests__/NoveraChatBanner.test.tsx (1)

21-26: Consider clearing mockNavigate between tests.

The mock function is not cleared between tests. If additional tests are added later, stale call data could cause flaky assertions.

🧹 Proposed fix
 const mockNavigate = vi.fn();
 
 // Mock react-router
 vi.mock("react-router", () => ({
   useNavigate: () => mockNavigate,
 }));
+
+beforeEach(() => {
+  mockNavigate.mockClear();
+});
apps/customer-portal/webapp/src/components/support/noveraAIAssistant/noveraChatPage/__tests__/ChatMessageList.test.tsx (1)

35-46: Test coverage is minimal; consider adding edge cases.

The current test only verifies that message text renders. Consider adding tests for:

  • Empty messages array
  • Different sender types (bot vs user) rendering differently
  • The messagesEndRef scroll behavior

Also, using any for the messages array loses type safety. If ChatMessage type is exported, prefer using it.

🧪 Example additional test
it("should render empty state when no messages", () => {
  const ref: any = { current: null };
  render(<ChatMessageList messages={[]} messagesEndRef={ref} />);
  
  // Verify container renders without errors
  expect(screen.getByTestId("box")).toBeInTheDocument();
});
apps/customer-portal/webapp/src/components/common/header/__tests__/ProjectSwitcher.test.tsx (1)

108-120: Consider adding test for undefined selectedProject.

The component handles selectedProject?.id || "" with optional chaining. A test verifying behavior when selectedProject is undefined would increase confidence in the fallback rendering (shows "Select Project" placeholder).

🧪 Example test
it("should render placeholder when no project is selected", () => {
  render(
    <ProjectSwitcher
      projects={mockProjects}
      selectedProject={undefined}
      onProjectChange={mockOnProjectChange}
    />,
  );

  expect(screen.getByTestId("project-select")).toHaveValue("");
});
apps/customer-portal/webapp/src/components/support/caseCreationLayout/__tests__/BasicInformationSection.test.tsx (1)

75-84: Consider clearing mock functions between tests.

The mock functions (setProject, setProduct, setDeployment) in defaultProps are shared across all tests without being cleared. This could cause test pollution if one test's assertions depend on call counts or previous calls from other tests.

♻️ Suggested improvement
+import { beforeEach } from "vitest";

 describe("BasicInformationSection", () => {
   const mockMetadata = {
     projects: ["Project 1", "Project 2"],
     products: ["Product 1", "Product 2"],
     deploymentTypes: ["Dev", "Prod"],
   };

-  const defaultProps = {
+  let defaultProps: ReturnType<typeof createDefaultProps>;
+
+  const createDefaultProps = () => ({
     project: "Project 1",
     setProject: vi.fn(),
     product: "Product 1",
     setProduct: vi.fn(),
     deployment: "Dev",
     setDeployment: vi.fn(),
     metadata: mockMetadata,
     isLoading: false,
-  };
+  });
+
+  beforeEach(() => {
+    defaultProps = createDefaultProps();
+  });
apps/customer-portal/webapp/src/components/support/caseCreationLayout/__tests__/CaseDetailsSection.test.tsx (1)

114-125: Consider clearing mock functions between tests.

Similar to BasicInformationSection.test.tsx, the mock functions in defaultProps are shared across tests without being reset. Use beforeEach to create fresh mocks for each test.

apps/customer-portal/webapp/src/components/dashboard/casesTable/__tests__/CasesList.test.tsx (1)

110-111: Consider clearing mock functions between tests.

mockOnPageChange and mockOnRowsPerPageChange are declared at module level and not cleared between tests. This could lead to accumulated call counts affecting assertions.

♻️ Suggested improvement
+import { beforeEach } from "vitest";

-  const mockOnPageChange = vi.fn();
-  const mockOnRowsPerPageChange = vi.fn();
+  let mockOnPageChange: ReturnType<typeof vi.fn>;
+  let mockOnRowsPerPageChange: ReturnType<typeof vi.fn>;
+
+  beforeEach(() => {
+    mockOnPageChange = vi.fn();
+    mockOnRowsPerPageChange = vi.fn();
+  });
apps/customer-portal/webapp/src/components/common/footer/__tests__/Footer.test.tsx (1)

46-49: Use consistent assertion methods.

Line 48 uses toBeDefined() while the other tests use toHaveAttribute(). For DOM element presence checks, toBeInTheDocument() is more idiomatic with @testing-library/react.

♻️ Suggested fix
   it("should render the company name", () => {
     render(<Footer />);
-    expect(screen.getByText(new RegExp(COMPANY_NAME, "i"))).toBeDefined();
+    expect(screen.getByText(new RegExp(COMPANY_NAME, "i"))).toBeInTheDocument();
   });
apps/customer-portal/webapp/src/pages/ProjectHub.tsx (1)

64-159: Consider extracting the repeated card wrapper styles.

The Box sx object is duplicated in both loading and loaded grids; a shared constant would reduce maintenance overhead.

♻️ Suggested refactor
+  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,
+  };
...
-            <Box
-              key={index}
-              sx={{
-                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,
-              }}
-            >
+            <Box key={index} sx={cardWrapperSx}>
...
-          <Box
-            key={project.id}
-            sx={{
-              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,
-            }}
-          >
+          <Box key={project.id} sx={cardWrapperSx}>
apps/customer-portal/webapp/src/components/support/casesOverviewStats/__tests__/CasesOverviewStatCard.test.tsx (1)

30-45: Dead code in StatCard mock.

The conditional logic checking for "Skeleton" in value (lines 31-34, 41) will never match because the component passes a React element (<Skeleton />) as value, not an object with a "Skeleton" property. The ValueSkeleton variable is never truthy. The mock still works correctly because React elements passed as value are rendered directly in the JSX.

Consider simplifying:

Suggested simplification
   StatCard: ({ label, value, icon }: any) => {
-    const ValueSkeleton =
-      value && typeof value === "object" && "Skeleton" in value
-        ? (value as any).Skeleton
-        : null;
-
     return (
       <div data-testid="oxygen-stat-card">
         <div data-testid="stat-card-icon">{icon}</div>
         <span>{label}</span>
-        <div data-testid="stat-card-value">
-          {ValueSkeleton ? <ValueSkeleton variant="text" /> : value}
-        </div>
+        <div data-testid="stat-card-value">{value}</div>
       </div>
     );
   },
apps/customer-portal/webapp/src/api/__tests__/useGetProjects.test.tsx (1)

89-99: Test title mentions query key verification but only asserts limit.

The test is titled "should use 'all' query key and larger limit when fetchAll is true" but only verifies the limit (100). The query key aspect isn't directly testable without inspecting QueryClient internals or spying on the query function.

Consider either updating the title to match what's tested, or adding a comment explaining why query key verification is omitted.

Suggested title update
-  it("should use 'all' query key and larger limit when fetchAll is true", async () => {
+  it("should use larger limit (100) when fetchAll is true", async () => {
apps/customer-portal/webapp/src/api/useGetProjectCases.ts (1)

57-63: Consider adding a TODO for production cleanup.

Per learnings, this fallback to mockCases.slice(0, limit) when filteredCases is empty is intentional for demo purposes. However, this behavior will need to be removed when integrating with the real API, as it would mask genuine "no data" scenarios.

Consider adding a TODO comment to document this:

+      // TODO: Remove fallback to mockCases when real API is integrated.
+      // This fallback exists only for demo purposes to always show data.
       const response: CaseSearchResponse = {
         cases: pagedCases.length > 0 ? pagedCases : mockCases.slice(0, limit),
apps/customer-portal/webapp/src/components/common/header/__tests__/Actions.test.tsx (1)

47-62: Remove unused icon mocks.

The Actions component only uses the Users icon, but this mock includes many unused icons (Briefcase, FileText, FolderOpen, Headset, Home, Megaphone, RefreshCw, Shield, Settings). Consider simplifying:

♻️ Suggested cleanup
 // Mock icons
 vi.mock("@wso2/oxygen-ui-icons-react", () => ({
-  Briefcase: mockIcon("Briefcase"),
-  FileText: mockIcon("FileText"),
-  FolderOpen: mockIcon("FolderOpen"),
-  Headset: mockIcon("Headset"),
-  Home: mockIcon("Home"),
-  Megaphone: mockIcon("Megaphone"),
-  RefreshCw: mockIcon("RefreshCw"),
-  Shield: mockIcon("Shield"),
   Users: () => <svg data-testid="icon-Users" />,
-  Settings: mockIcon("Settings"),
 });
apps/customer-portal/webapp/src/pages/__tests__/NoveraChatPage.test.tsx (2)

114-114: Consider a more robust selector for the send button.

Using screen.getByTestId("send-icon").parentElement! with a non-null assertion is fragile. If the DOM structure changes, this will throw a runtime error rather than a clear test failure.

Consider adding a data-testid to the IconButton mock or using a more descriptive approach:

♻️ Suggested improvement
-  IconButton: ({ children, onClick, disabled }: any) => (
-    <button onClick={onClick} disabled={disabled}>
+  IconButton: ({ children, onClick, disabled, "aria-label": ariaLabel }: any) => (
+    <button onClick={onClick} disabled={disabled} aria-label={ariaLabel}>
       {children}
     </button>
   ),

Then in tests:

-    const sendButton = screen.getByTestId("send-icon").parentElement!;
+    const sendButton = screen.getByRole("button", { name: /send/i });

116-132: Comments show incorrect message counts.

The comments don't account for the initial bot message. With the initial bot message, the actual counts are:

  • After 1st user message: 2 messages (1 bot + 1 user)
  • After 2nd user message: 3 messages
  • After 3rd user message: 4 messages
  • After 4th user message: 5 messages → triggers escalation (> 4)

The test logic is correct, but the comments are misleading.

apps/customer-portal/webapp/src/components/dashboard/casesTable/CasesTableHeader.tsx (2)

33-33: Remove unused projectId prop.

The projectId prop is declared in the interface and destructured but never used in the component. Either remove it or implement its intended usage.

♻️ Suggested fix
 interface CasesTableHeaderProps {
   activeFiltersCount: number;
   appliedFilters: Record<string, string>;
   filterFields: ActiveFilterConfig[];
   onRemoveFilter: (field: string) => void;
   onClearAll: () => void;
   onUpdateFilter: (field: string, value: any) => void;
   onFilterClick: () => void;
   onCreateCase: () => void;
-  projectId: string;
 }

 const CasesTableHeader = ({
   activeFiltersCount,
   appliedFilters,
   filterFields,
   onRemoveFilter,
   onClearAll,
   onUpdateFilter,
   onFilterClick,
   onCreateCase,
-}: CasesTableHeaderProps): JSX.Element => {
+}: Omit<CasesTableHeaderProps, 'projectId'>): JSX.Element => {

Or if the prop will be needed later, add a TODO comment.

Also applies to: 45-45


95-97: "All cases" button has no click handler.

This button appears to be a placeholder without functionality. Consider either implementing the handler or adding a TODO comment to track this.

-        <Button variant="outlined" size="small" color="warning">
+        {/* TODO: Implement navigation to all cases view */}
+        <Button variant="outlined" size="small" color="warning" disabled>
           All cases
         </Button>
apps/customer-portal/webapp/src/components/support/caseCreationLayout/BasicInformationSection.tsx (1)

30-38: Type metadata to avoid any.

Using a concrete type here improves safety and auto-complete for the select options.

🧩 Suggested typing update
 import { Pencil, Sparkles } from "@wso2/oxygen-ui-icons-react";
 import type { JSX } from "react";
+import type { CaseCreationMetadata } from "@/models/mockData";
@@
-  metadata: any;
+  metadata: CaseCreationMetadata | undefined;
apps/customer-portal/webapp/src/components/dashboard/casesTable/__tests__/CasesTableHeader.test.tsx (1)

45-56: Consider adding beforeEach to reset mock functions.

The mockProps callbacks (onCreateCase, onFilterClick, etc.) are not reset between tests. If tests run in a specific order and one test checks call counts, it could lead to flaky results.

Proposed fix
+import { beforeEach, describe, expect, it, vi } from "vitest";
+
+beforeEach(() => {
+  vi.clearAllMocks();
+});
+
 describe("CasesTableHeader", () => {
   const mockProps = {
apps/customer-portal/webapp/src/components/common/sideNavBar/SubscriptionWidget.tsx (2)

65-72: Placeholder text appears incomplete.

The text "Information about" / "subscription" seems like placeholder content. Consider replacing with meaningful copy or adding a TODO comment if this is intentional.


39-79: Remove unnecessary Fragment wrapper.

The Fragment (<>...</>) wrapping the Paper is unnecessary since there's only one root element.

Proposed fix
-  return (
-    <>
-      {/* subscription widget container */}
-      <Paper
+  return (
+    <Paper
         sx={{
           p: 1.5,
           m: 1.5,
           border: "1px solid",
           borderColor: "divider",
         }}
       >
         ...
-      </Paper>
-    </>
-  );
+    </Paper>
+  );
apps/customer-portal/webapp/src/components/support/caseCreationLayout/ConversationSummary.tsx (1)

27-27: Type imported from mockData module.

CaseCreationMetadata is imported from @/models/mockData. If this type will be used in production, consider moving it to a dedicated types/models file to avoid coupling production code to mock data modules.

apps/customer-portal/webapp/src/components/dashboard/stats/StatCard.tsx (2)

83-87: Redundant type assertion.

The iconColor as StatCardColor cast is unnecessary since iconColor is already typed as StatCardColor in the props interface (line 37).

Suggested simplification
           bgcolor: alpha(
-              theme.palette[iconColor as StatCardColor].light,
+              theme.palette[iconColor].light,
               0.1,
             ),
-            color: theme.palette[iconColor as StatCardColor].light,
+            color: theme.palette[iconColor].light,

116-118: Consider wrapping the icon in a span for Tooltip compatibility.

Some icon components don't forward refs, which can cause MUI/Oxygen Tooltip to log a warning. Wrapping in a <span> ensures proper ref forwarding.

Suggested fix
           <Tooltip title={tooltipText} arrow placement="bottom">
-            <Info size={14} />
+            <span style={{ display: "inline-flex" }}>
+              <Info size={14} />
+            </span>
           </Tooltip>
apps/customer-portal/webapp/src/pages/CreateCasePage.tsx (1)

92-94: Form submission handler is a no-op.

The handleSubmit function only prevents default behavior without implementing actual case creation logic. This is acceptable for a WIP page, but consider adding a TODO comment for clarity.

Would you like me to open an issue to track implementing the case submission logic?

apps/customer-portal/webapp/src/components/dashboard/casesTable/CasesTable.tsx (2)

33-33: Consider adding a typed filter state interface.

Using Record<string, any> for filters loses type safety. A dedicated interface would provide better autocomplete and catch errors at compile time.

Suggested type definition
+interface CaseFilters {
+  deploymentId?: string;
+  severityId?: string;
+  statusId?: string;
+  caseTypes?: string;
+}
+
 const CasesTable = ({ projectId }: CasesTableProps): JSX.Element => {
   const navigate = useNavigate();
-  const [filters, setFilters] = useState<Record<string, any>>({});
+  const [filters, setFilters] = useState<CaseFilters>({});

101-105: Consider memoizing or simplifying activeFilterFields.

This mapping creates a new array on every render. If CasesTableHeader only needs id, label, and options, consider either:

  1. Passing FILTER_FIELDS directly if the extra type property is harmless
  2. Memoizing with useMemo if transformation is necessary
Option 1: Pass FILTER_FIELDS directly
       <CasesTableHeader
         activeFiltersCount={Object.keys(filters).length}
         appliedFilters={mappedAppliedFilters}
-        filterFields={activeFilterFields}
+        filterFields={FILTER_FIELDS}
Option 2: Memoize the transformation
-  const activeFilterFields = FILTER_FIELDS.map((field) => ({
-    id: field.id,
-    label: field.label,
-    options: field.options,
-  }));
+  const activeFilterFields = useMemo(
+    () =>
+      FILTER_FIELDS.map((field) => ({
+        id: field.id,
+        label: field.label,
+        options: field.options,
+      })),
+    [],
+  );
apps/customer-portal/webapp/src/api/useGetProjects.ts (1)

44-46: Consider cache key collision with "default" fallback.

When searchData is undefined, the queryKey falls back to "default", which could cause unintended cache sharing between different components calling useGetProjects() without parameters. This may lead to stale or incorrect data being served.

Consider using a more explicit fallback or requiring searchData:

  const queryKey = fetchAll
    ? [ApiQueryKeys.PROJECTS, "all"]
-   : [ApiQueryKeys.PROJECTS, searchData ?? "default"];
+   : [ApiQueryKeys.PROJECTS, searchData ?? {}];
apps/customer-portal/webapp/src/components/common/header/ProjectSwitcher.tsx (2)

74-74: Avoid any type for event handler.

Using any bypasses TypeScript's type checking. Consider using the proper event type from the UI library or a more specific type.

♻️ Proposed fix
-       onChange={(event: any) => onProjectChange(event.target.value)}
+       onChange={(event: React.ChangeEvent<{ value: unknown }>) => 
+         onProjectChange(event.target.value as string)
+       }

Or if the library provides a specific type, use that instead.


69-101: Consider handling empty projects array.

When projects is empty and isLoading is false, the dropdown renders with no items and shows "Select Project". Consider showing an informative message or disabling the dropdown when no projects are available.

apps/customer-portal/webapp/src/pages/DashboardPage.tsx (3)

17-17: Unused import: LinearProgress.

LinearProgress is imported but not used in this component since the global loader context handles the loading indicator.

♻️ Proposed fix
-import { Box, Button, Grid, LinearProgress, Typography } from "@wso2/oxygen-ui";
+import { Box, Button, Grid, Typography } from "@wso2/oxygen-ui";

157-157: TODO: Error component for chart loading failures.

This TODO indicates incomplete error handling for charts. Tracking this for follow-up.

Would you like me to open an issue to track implementing a dedicated error component for chart loading failures?


117-155: Consider extracting stat value resolution to a utility function.

The switch statement for mapping stat IDs to values is verbose. A mapping object or utility function would improve maintainability.

♻️ Suggested refactor
const getStatValue = (statId: string, casesStats: ProjectCasesStats | undefined): string | number => {
  if (!casesStats) return 0;
  const valueMap: Record<string, string | number> = {
    totalCases: casesStats.totalCases,
    openCases: casesStats.openCases,
    resolvedCases: casesStats.resolvedCases.total,
    avgResponseTime: `${casesStats.averageResponseTime}h`,
  };
  return valueMap[statId] ?? 0;
};
apps/customer-portal/webapp/src/components/dashboard/casesTable/CasesList.tsx (2)

85-89: Fragile date/time parsing.

Splitting on space assumes a specific date format (e.g., "2024-01-15 10:30:00"). If the format varies or includes timezone info, this will break. Consider using a date parsing library or utility function.

♻️ Proposed improvement
// Utility function for safer date parsing
const parseDateTime = (dateString: string) => {
  const date = new Date(dateString);
  return {
    date: date.toLocaleDateString(),
    time: date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }),
  };
};

114-118: TODO: Contact column placeholder.

The Contact column currently shows "TODO". This should be tracked for completion.

Would you like me to open an issue to track implementing the Contact column?

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

71-76: Mock values generated even when props are provided.

The mock functions are called unconditionally via useMemo, even when status, openCases, and activeChats are provided as props. While the empty dependency arrays prevent re-execution, the initial generation is still wasteful.

♻️ Proposed lazy initialization
- const mockStatus = useMemo(() => getMockStatus(), []);
- const mockOpenCases = useMemo(() => getMockOpenCases(), []);
- const mockActiveChats = useMemo(() => getMockActiveChats(), []);
- const resolvedStatus = status ?? mockStatus;
- const resolvedOpenCases = openCases ?? mockOpenCases;
- const resolvedActiveChats = activeChats ?? mockActiveChats;
+ const resolvedStatus = useMemo(
+   () => status ?? getMockStatus(),
+   [status]
+ );
+ const resolvedOpenCases = useMemo(
+   () => openCases ?? getMockOpenCases(),
+   [openCases]
+ );
+ const resolvedActiveChats = useMemo(
+   () => activeChats ?? getMockActiveChats(),
+   [activeChats]
+ );

Note: This changes behavior slightly - mock values would regenerate if props change from defined to undefined. If stable mock values are required across re-renders, the current approach is acceptable for demo purposes.

apps/customer-portal/webapp/src/models/responses.ts (1)

96-102: Consider more descriptive property names for casesTrend.

The properties TypeA, TypeB, TypeC, TypeD are generic. If these represent specific case categories (e.g., incidents, questions, bugs, features), consider using descriptive names for better code readability and maintainability.

apps/customer-portal/webapp/src/models/mockData.ts (1)

90-96: Nitpick: Consider lowercase email for consistency.

Email addresses are conventionally lowercase. While not functionally incorrect, john@example.com would be more realistic.

apps/customer-portal/webapp/src/models/mockFunctions.ts (1)

163-180: Verify trend color semantics for decreasing metrics.

The trend colors appear semantically inverted:

  • openCases decreasing with color: "error" — typically fewer open cases is positive ("success")
  • avgResponseTime decreasing with color: "error" — typically faster response times is positive ("success")

If this is intentional design (e.g., highlighting any change as noteworthy), please disregard. Otherwise, consider:

♻️ Proposed fix for semantic colors
     openCases: {
       value: 42,
-      trend: { value: "5%", direction: "down", color: "error" },
+      trend: { value: "5%", direction: "down", color: "success" },
     },
     resolvedCases: {
       value: 114,
       trend: { value: "8%", direction: "up", color: "success" },
     },
     avgResponseTime: {
       value: "4.5h",
-      trend: { value: "0.5h", direction: "down", color: "error" },
+      trend: { value: "0.5h", direction: "down", color: "success" },
     },

Comment thread apps/customer-portal/webapp/src/pages/CreateCasePage.tsx Outdated
Comment thread apps/customer-portal/webapp/src/utils/projectCard.ts
@dileepapeiris
dileepapeiris marked this pull request as draft February 3, 2026 01:42
Introduce a new test suite for the useGetProjectDetails hook using Vitest and @testing-library/react. Adds a QueryClientProvider wrapper (no retries), mocks API_MOCK_DELAY to 0 and useLogger, and includes tests for a valid project ID (success path) and an invalid project ID (error path).
Introduce a new Vitest test file for the useGetProjectStat hook. The tests add a QueryClientProvider wrapper, mock API delay and useLogger, and verify both a successful fetch for a valid project ID and an error case for an invalid ID. Includes license header and uses existing mockProjects test data.
Introduce a new custom hook (apps/customer-portal/webapp/src/api/useGetProjectDetails.ts) that fetches project details by ID using @tanstack/react-query. The hook uses mockProjectDetails and simulates network latency with API_MOCK_DELAY, logs actions via useLogger, and throws an error if the project ID is not found. Query is keyed by ApiQueryKeys.PROJECT_DETAILS, enabled only when projectId is provided, and sets a 5-minute staleTime.
Add a new Vitest test suite at apps/customer-portal/webapp/src/utils/__tests__/projectStats.test.ts covering formatProjectDate, getSLAStatusColor, getSupportTierColor, getProjectTypeColor, getSystemHealthColor, getSubscriptionStatus, and getSubscriptionColor. Tests verify string casing, empty/null handling, ISO/full-date parsing, and subscription status edge cases by using fake timers (fixed system time). File includes project license header.
Add a new projectStats utility module providing helpers for the customer-portal UI: formatProjectDate (formats dates to "MMM D, YYYY"), multiple get*Color functions that map SLA/support tier/project type/system health/subscription statuses to chip color tokens (with unknown values defaulting to "default"), and getSubscriptionStatus which classifies an end date as Active/Expiring Soon/Expired (30-day threshold). These helpers centralize presentation logic for project stats and chip styling.
Replace placeholder overview with real project overview UI and data fetching. Add imports for Grid, useOutletContext, useEffect, API hooks, logger and loader context. Fetch project details and stats (useGetProjectDetails, useGetProjectStat), show/hide a global loader while loading, and log errors. Render ProjectInformationCard, ProjectStatisticsCard, ContactInfoCard, and RecentActivityCard in a responsive Grid and handle invalid projectId. Simplify outer layout (use fragment) and pass sidebarCollapsed to statistics card for responsive behavior.
Fetch project details and integrate them into CreateCasePage: add useGetProjectDetails, show loader while project data loads, and set the project name from projectDetails without overwriting a user-selected project or when projectId is present. Include isProjectLoading in loader and isLoading props, and add an effect to update the local project state. Misc UI/cleanup: remove an unused Divider import/usage and extra padding, change the submit button color to primary, remove unused LinearProgress import in DashboardPage, and export the Message interface in NoveraChatPage.
Add mocks and adjustments to CreateCasePage unit tests: import and mock the useGetProjectDetails hook (providing sample project data), add IconButton and PencilLine mocks for @wso2/oxygen-ui and icon package, and update a test to simulate a failing project-details return. These changes stabilize tests by providing predictable project data and required UI component mocks.
Introduce a new React Query hook useGetProjectStat to fetch project statistics. The hook uses mock data (getMockProjectStats, mockProjects) with a simulated network delay, validates the provided projectId and throws an error if not found, and logs operations via useLogger. Query is keyed by ApiQueryKeys.PROJECT_STATS and is enabled only when projectId is truthy, with a 5-minute staleTime. File includes Apache-2.0 license header.
Change the ActiveFilters chip color from primary to warning when a filter is active. Improve selection detection by matching appliedFilters against both the display label and the option value. Remove the now-unused projectId prop from CasesTableHeader and update the test to reflect that removal.
Extend response models to support richer project and user data. Adds ProjectDetails (including subscription start/end and supportTier), makes UserProfile.status optional, adds optional projects list to CaseSearchResponse, and introduces ProjectStatsResponse with projectStats and recentActivity fields to support dashboard/metrics use cases. Changes live in apps/customer-portal/webapp/src/models/responses.ts.
Add ProjectName.test.tsx to verify ProjectName behavior using vitest and @testing-library/react. The file mocks @wso2/oxygen-ui components and contains tests for non-loading state (renders label, project name and key) and loading state (renders skeletons and hides name/key). Located under projectInformation/__tests__.
Rashmika998
Rashmika998 previously approved these changes Feb 4, 2026

@Rashmika998 Rashmika998 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.

LGTM
directory refactor and path changes will be tracked under #89

@shayanmalinda

Copy link
Copy Markdown
Contributor

@dileepapeiris Are we good to merge this?

Import SubscriptionStatus from models and switch getSubscriptionStatus to return the enum instead of string literals. Update getSubscriptionColor to accept SubscriptionStatus | string and compare against enum values (normalized with toLowerCase()) for consistent color mapping. These changes improve type safety and centralize status values while preserving existing behavior.
Introduce a SubscriptionStatus constant and corresponding TypeScript union type in apps/customer-portal/webapp/src/models/responses.ts. The new exported const provides three statuses (Expired, "Expiring Soon", Active) and the exported type alias derives a union of those values to improve type safety for subscription status fields in API responses.
Replace string literal checks for "Expired" with SubscriptionStatus.Expired in SubscriptionDetails component. Adds import from @/models/responses and updates two comparisons (progress calculation and label text) to improve type-safety and avoid magic strings.
Replace hardcoded 'Active'/'Expired' strings in SubscriptionDetails tests with the SubscriptionStatus enum. Adds import for SubscriptionStatus and updates mocked getSubscriptionStatus/getSubscriptionColor implementations and assertions to use SubscriptionStatus.Active/SubscriptionStatus.Expired. No behavioral change — just aligns tests with the model enum.
@dileepapeiris

Copy link
Copy Markdown
Contributor Author

@dileepapeiris Are we good to merge this?

@shayanmalinda Yes

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

🤖 Fix all issues with AI agents
In
`@apps/customer-portal/webapp/src/components/projectDetails/projectOverview/projectInformation/__tests__/SubscriptionDetails.test.tsx`:
- Around line 74-77: Update the stale inline comment in
SubscriptionDetails.test.tsx that says "Active -> 75" to match the mock's actual
return value of 50 (or remove the comment entirely); ensure the test assertions
around the progress element (expect(progress).toHaveAttribute("data-value",
"50") and expect(progress).toHaveAttribute("data-color", "success")) remain
unchanged and reflect the mock for active subscriptions so the comment
accurately documents the mock behavior.
🧹 Nitpick comments (1)
apps/customer-portal/webapp/src/utils/projectStats.ts (1)

132-156: Consider injecting "today" for testability.

getSubscriptionStatus creates new Date() internally, making it non-deterministic and harder to unit test without mocking Date. Consider accepting an optional referenceDate parameter.

♻️ Suggested refactor for testability
 export const getSubscriptionStatus = (
   endDateString: string,
+  referenceDate: Date = new Date(),
 ): SubscriptionStatus => {
   if (!endDateString) {
     return SubscriptionStatus.Active;
   }

-  const today = new Date();
+  const today = referenceDate;
   const endDate = new Date(endDateString);

Delete a misleading comment that referenced a default mock value of 75 in the SubscriptionDetails test. The test actually asserts a data-value of 50, so removing the stale comment reduces confusion.
Comment thread apps/customer-portal/webapp/src/models/responses.ts Outdated
Comment thread apps/customer-portal/webapp/src/models/responses.ts Outdated
Comment thread apps/customer-portal/webapp/src/utils/projectStats.ts Outdated
Replace ad-hoc enums and string literals with centralized constants from projectDetailsConstants. Update imports and refactor SLA, support tier, project type, system health, and subscription status checks (including getSubscriptionStatus and color mapping helpers) to use these constants for consistent comparisons and return values.
Delete the SubscriptionStatus constant and its corresponding TypeScript type from apps/customer-portal/webapp/src/models/responses.ts. This cleans up the responses model by removing the no-longer-needed subscription status definition (likely moved or unused).
Import PROJECT_TYPE, SUPPORT_TIER, and CASE_STATUS and replace hard-coded string literals in mockProjectDetails and mockCases with these constants in apps/customer-portal/webapp/src/models/mockData.ts. This reduces magic strings, improves consistency with project constants, and avoids typos; no functional behavior changes.
Introduce centralized constants and corresponding TypeScript union types in projectDetailsConstants.tsx for subscription status, support tier, project type, system health, SLA status, case priority, and case status. Each constant uses `as const` so the derived types are literal unions, providing stricter, consistent typing for project detail logic and UI components.
Replace SubscriptionStatus import with SUBSCRIPTION_STATUS from projectDetailsConstants in SubscriptionDetails.tsx. Update conditional checks to use SUBSCRIPTION_STATUS.EXPIRED when computing progress and rendering the expiration label, centralizing subscription status usage.
Replace direct SubscriptionStatus import with SUBSCRIPTION_STATUS from projectDetailsConstants and update test mocks and assertions accordingly. Adjusted mocked @wso2/oxygen-ui to include a colors object, and updated utility mocks (getSubscriptionStatus, getSubscriptionColor) and expected text values to use the new constant values so tests reflect the refactor to centralized status constants.
Drop the unused `collapsed` parameter from Header's function signature and remove the `sx` prop from the mocked Card in ProjectStatisticsCard tests. This cleans up unused/unused-typed props and aligns the test mock with the Card API, resolving lint/TS warnings without changing runtime behavior.
@Rashmika998
Rashmika998 merged commit aaf46f3 into wso2-open-operations:customer-portal-milestone-1 Feb 4, 2026
1 check passed
@dileepapeiris

Copy link
Copy Markdown
Contributor Author

LGTM directory refactor and path changes will be tracked under #89

Those changes were addressed via #94 and #95

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

App/Customer Portal Area/Frontend Platform/Web Type/New Feature Represents a request or task for a new feature Type/Task General task that does not fit into other categories Type/UX Refers to user experience-related tasks or issues

Projects

Status: Staging Deployed

Development

Successfully merging this pull request may close these issues.

4 participants