From 7de31f857353e0d57d5f8968792d8d66292630d4 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 11:22:49 +0530 Subject: [PATCH 01/20] Simplify Grid props in CasesOverviewStatCard Collapse the Grid element props into a single-line declaration and remove the explicit sx={{ mb: 3 }} styling. This simplifies the component markup in apps/customer-portal/webapp/src/components/support/cases-overview-stats/CasesOverviewStatCard.tsx without changing functionality. --- .../cases-overview-stats/CasesOverviewStatCard.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/apps/customer-portal/webapp/src/components/support/cases-overview-stats/CasesOverviewStatCard.tsx b/apps/customer-portal/webapp/src/components/support/cases-overview-stats/CasesOverviewStatCard.tsx index a119fd3e9b..7210f51e4b 100644 --- a/apps/customer-portal/webapp/src/components/support/cases-overview-stats/CasesOverviewStatCard.tsx +++ b/apps/customer-portal/webapp/src/components/support/cases-overview-stats/CasesOverviewStatCard.tsx @@ -36,13 +36,7 @@ export default function CasesOverviewStatCard({ }: CasesOverviewStatCardProps): JSX.Element { return ( - + {SUPPORT_STAT_CONFIGS.map((stat) => { const SecondaryIcon = stat.secondaryIcon; From 15bbc453b9524be2ae102b9f1c4df9b50c728843 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 11:22:58 +0530 Subject: [PATCH 02/20] Remove bottom margin from NoveraChatBanner Remove the `mb: 8` style from the Paper component in NoveraChatBanner to eliminate the extra bottom margin and adjust spacing/alignment. No functional logic changes. --- .../novera-ai-assistant/novera-chat-banner/NoveraChatBanner.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-banner/NoveraChatBanner.tsx b/apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-banner/NoveraChatBanner.tsx index f01df79b98..204861e612 100644 --- a/apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-banner/NoveraChatBanner.tsx +++ b/apps/customer-portal/webapp/src/components/support/novera-ai-assistant/novera-chat-banner/NoveraChatBanner.tsx @@ -30,7 +30,6 @@ export default function NoveraChatBanner(): JSX.Element { return ( Date: Mon, 9 Feb 2026 11:23:14 +0530 Subject: [PATCH 03/20] Add tests for ChatHistoryList component Add a new unit test file for the ChatHistoryList component (apps/customer-portal/webapp/src/components/support/support-overview-cards/__tests__/ChatHistoryList.test.tsx). Tests verify empty state rendering, display of chat titles, metadata (time, messages, KB articles) and status, presence of View/Resume buttons, and that the onItemAction callback is invoked with the correct chatId and action. Tests use vitest and React Testing Library with mocked chat items. --- .../__tests__/ChatHistoryList.test.tsx | 80 +++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 apps/customer-portal/webapp/src/components/support/support-overview-cards/__tests__/ChatHistoryList.test.tsx diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/__tests__/ChatHistoryList.test.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/__tests__/ChatHistoryList.test.tsx new file mode 100644 index 0000000000..cc28884eb2 --- /dev/null +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/__tests__/ChatHistoryList.test.tsx @@ -0,0 +1,80 @@ +// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import { render, screen, fireEvent } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import ChatHistoryList from "@components/support/support-overview-cards/ChatHistoryList"; +import type { ChatHistoryItem } from "@models/responses"; + +const mockItems: ChatHistoryItem[] = [ + { + chatId: "1628192673", + title: "How do I configure custom claims in JWT tokens?", + startedTime: "2 hours ago", + messages: 8, + kbArticles: 3, + status: "Resolved", + }, + { + chatId: "1628192674", + title: "Getting error 401 when calling the API endpoint...", + startedTime: "1 day ago", + messages: 5, + kbArticles: 2, + status: "Still Open", + }, +]; + +describe("ChatHistoryList", () => { + it("should render empty state when no items", () => { + render(); + expect(screen.getByText("No chat history.")).toBeInTheDocument(); + }); + + it("should render chat title, meta and status", () => { + render(); + expect( + screen.getByText("How do I configure custom claims in JWT tokens?"), + ).toBeInTheDocument(); + expect( + screen.getByText("Getting error 401 when calling the API endpoint..."), + ).toBeInTheDocument(); + expect(screen.getByText("Resolved")).toBeInTheDocument(); + expect(screen.getByText("Still Open")).toBeInTheDocument(); + expect(screen.getByText("2 hours ago")).toBeInTheDocument(); + expect(screen.getByText(/8 messages/)).toBeInTheDocument(); + expect(screen.getByText(/3 KB articles/)).toBeInTheDocument(); + }); + + it("should show View for Resolved and Resume for Still Open", () => { + render(); + const viewButtons = screen.getAllByRole("button", { name: /view/i }); + const resumeButtons = screen.getAllByRole("button", { name: /resume/i }); + expect(viewButtons.length).toBeGreaterThanOrEqual(1); + expect(resumeButtons.length).toBeGreaterThanOrEqual(1); + }); + + it("should call onItemAction with chatId and action when View/Resume clicked", () => { + const onItemAction = vi.fn(); + render(); + + fireEvent.click(screen.getByRole("button", { name: "View" })); + expect(onItemAction).toHaveBeenCalledWith("1628192673", "view"); + + fireEvent.click(screen.getByRole("button", { name: "Resume" })); + expect(onItemAction).toHaveBeenCalledWith("1628192674", "resume"); + }); +}); From d581b0052765f635d73f20a7914ebfb1be032a85 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 11:23:44 +0530 Subject: [PATCH 04/20] Add tests for support utils Add a new vitest test file at apps/customer-portal/webapp/src/utils/__tests__/support.test.ts that validates support utility functions. Tests cover getChatStatusAction (status->action mapping), getChatStatusChipSx and getChatStatusRowSx (styling outputs per status), and formatRelativeTime (handling undefined, just now, minutes/hours/days ago). --- .../src/utils/__tests__/support.test.ts | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 apps/customer-portal/webapp/src/utils/__tests__/support.test.ts diff --git a/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts b/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts new file mode 100644 index 0000000000..02633ccd50 --- /dev/null +++ b/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts @@ -0,0 +1,96 @@ +// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import { describe, expect, it } from "vitest"; +import { + getChatStatusAction, + getChatStatusChipSx, + getChatStatusRowSx, + formatRelativeTime, +} from "@utils/support"; + +describe("support utils", () => { + describe("getChatStatusAction", () => { + it("should return 'resume' for Still Open", () => { + expect(getChatStatusAction("Still Open")).toBe("resume"); + expect(getChatStatusAction("still open")).toBe("resume"); + }); + + it("should return 'view' for Resolved and Abandoned", () => { + expect(getChatStatusAction("Resolved")).toBe("view"); + expect(getChatStatusAction("Abandoned")).toBe("view"); + expect(getChatStatusAction("")).toBe("view"); + }); + }); + + describe("getChatStatusChipSx", () => { + it("should return green sx for Resolved", () => { + const sx = getChatStatusChipSx("Resolved") as any; + expect(sx.bgcolor).toBeDefined(); + expect(sx.color).toBeDefined(); + expect(sx.borderColor).toBeDefined(); + }); + + it("should return blue sx for Still Open", () => { + const sx = getChatStatusChipSx("Still Open") as any; + expect(sx.bgcolor).toBeDefined(); + expect(sx.color).toBeDefined(); + }); + + it("should return grey sx for Abandoned", () => { + const sx = getChatStatusChipSx("Abandoned") as any; + expect(sx.bgcolor).toBeDefined(); + }); + }); + + describe("getChatStatusRowSx", () => { + it("should return green tint for Resolved", () => { + const sx = getChatStatusRowSx("Resolved") as any; + expect(sx.bgcolor).toBeDefined(); + }); + + it("should return blue tint for Still Open", () => { + const sx = getChatStatusRowSx("Still Open") as any; + expect(sx.bgcolor).toBeDefined(); + }); + }); + + describe("formatRelativeTime", () => { + it("should return '--' for undefined date", () => { + expect(formatRelativeTime(undefined)).toBe("--"); + }); + + it("should return 'just now' for very recent dates", () => { + const now = new Date(); + expect(formatRelativeTime(now)).toBe("just now"); + }); + + it("should format minutes ago", () => { + const fiveMinsAgo = new Date(Date.now() - 5 * 60 * 1000); + expect(formatRelativeTime(fiveMinsAgo)).toBe("5 minutes ago"); + }); + + it("should format hours ago", () => { + const twoHoursAgo = new Date(Date.now() - 2 * 60 * 60 * 1000); + expect(formatRelativeTime(twoHoursAgo)).toBe("2 hours ago"); + }); + + it("should format days ago", () => { + const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000); + expect(formatRelativeTime(threeDaysAgo)).toBe("3 days ago"); + }); + }); +}); From 447e96a1d4c6ba1a09d1fc3ef91e7b3d4f6bcaac Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 11:24:44 +0530 Subject: [PATCH 05/20] Add Chat History overview to Support page Add a Chat History overview card to the Support page. Imports useGetChatHistory, ChatHistoryList and SUPPORT_OVERVIEW_CHAT_LIMIT; fetches recent chat history (sliced to the configured limit) and exposes loading state. Adds a new SupportOverviewCard for "Chat History" that navigates to the full chat view and passes chatId when an item is selected. Also updates the Outstanding Cases subtitle to use the cases limit constant, replaces Box with Stack and adjusts Grid spacing and flex styles to ensure cards stretch consistently. --- .../webapp/src/pages/SupportPage.tsx | 40 ++++++++++++++++--- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/apps/customer-portal/webapp/src/pages/SupportPage.tsx b/apps/customer-portal/webapp/src/pages/SupportPage.tsx index 0a38441b0c..ec24c2bba3 100644 --- a/apps/customer-portal/webapp/src/pages/SupportPage.tsx +++ b/apps/customer-portal/webapp/src/pages/SupportPage.tsx @@ -16,18 +16,21 @@ import { useParams, useNavigate } from "react-router"; import { useEffect, type JSX } from "react"; -import { Typography, Box, Grid } from "@wso2/oxygen-ui"; +import { Typography, Box, Grid, Stack } from "@wso2/oxygen-ui"; import { FileText, MessageSquare } from "@wso2/oxygen-ui-icons-react"; import { useAsgardeo } from "@asgardeo/react"; import CasesOverviewStatCard from "@components/support/cases-overview-stats/CasesOverviewStatCard"; import NoveraChatBanner from "@components/support/novera-ai-assistant/novera-chat-banner/NoveraChatBanner"; import SupportOverviewCard from "@components/support/support-overview-cards/SupportOverviewCard"; import OutstandingCasesList from "@components/support/support-overview-cards/OutstandingCasesList"; +import ChatHistoryList from "@components/support/support-overview-cards/ChatHistoryList"; import { useGetProjectSupportStats } from "@api/useGetProjectSupportStats"; import useGetProjectCases from "@api/useGetProjectCases"; +import { useGetChatHistory } from "@api/useGetChatHistory"; import { useLogger } from "@hooks/useLogger"; import { SUPPORT_OVERVIEW_CASES_LIMIT, + SUPPORT_OVERVIEW_CHAT_LIMIT, } from "@constants/supportConstants"; /** @@ -53,9 +56,16 @@ export default function SupportPage(): JSX.Element { sortBy: { field: "createdOn", order: "desc" }, }, ); + + const { data: chatHistoryData, isFetching: isChatLoading } = + useGetChatHistory(projectId || ""); + const { isLoading: isAuthLoading } = useAsgardeo(); const cases = casesData?.cases ?? []; + const chatItems = + chatHistoryData?.chatHistory?.slice(0, SUPPORT_OVERVIEW_CHAT_LIMIT) ?? []; + const isActuallyLoading = isAuthLoading || isFetching || (!stats && !isError); useEffect(() => { @@ -81,14 +91,14 @@ export default function SupportPage(): JSX.Element { } return ( - + - - + + + + navigate("chat")} + > + { + navigate("chat", { state: { chatId } }); + }} + /> + + - + ); } From 25aff2af058d671876a6d073b2933f7ebc46a3ec Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 11:24:59 +0530 Subject: [PATCH 06/20] tests: mock deps and add SupportPage test Add React import and several mocks to stabilize SupportPage unit tests: mock @asgardeo/react to avoid ESM buffer import errors, provide simplified @wso2/oxygen-ui theme utilities, mock API hooks (useGetProjectCases, useGetChatHistory), and replace complex overview card components with lightweight test doubles. Also add a new test asserting Outstanding Cases and Chat History overview cards render when data is loaded. These changes make the test suite independent of heavy UI/provider trees and external ESM issues. --- .../src/pages/__tests__/SupportPage.test.tsx | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/apps/customer-portal/webapp/src/pages/__tests__/SupportPage.test.tsx b/apps/customer-portal/webapp/src/pages/__tests__/SupportPage.test.tsx index 2182970730..fa31454add 100644 --- a/apps/customer-portal/webapp/src/pages/__tests__/SupportPage.test.tsx +++ b/apps/customer-portal/webapp/src/pages/__tests__/SupportPage.test.tsx @@ -14,6 +14,7 @@ // specific language governing permissions and limitations // under the License. +import React, { type ReactElement } from "react"; import { render, screen } from "@testing-library/react"; import { MemoryRouter } from "react-router"; import { beforeEach, describe, expect, it, vi } from "vitest"; @@ -29,6 +30,11 @@ vi.mock("react-router", async () => { }; }); +// Mock @asgardeo/react to avoid ESM buffer import errors in vitest +vi.mock("@asgardeo/react", () => ({ + useAsgardeo: () => ({ isLoading: false, isSignedIn: true }), +})); + // Mock useLogger const mockLogger = { debug: vi.fn(), @@ -92,6 +98,18 @@ vi.mock("@wso2/oxygen-ui", () => ({ ), Paper: ({ children }: any) =>
{children}
, + alpha: (color: string, opacity: number) => `alpha(${color}, ${opacity})`, + useTheme: () => ({ + palette: { + primary: { main: "#0070F3" }, + secondary: { main: "#71717A" }, + error: { main: "#EF4444" }, + warning: { main: "#F59E0B" }, + info: { main: "#3B82F6" }, + success: { main: "#10B981" }, + text: { primary: "#000000", secondary: "#6B7280" }, + }, + }), })); // Mock icons @@ -118,6 +136,48 @@ vi.mock("@/api/useGetProjectSupportStats", () => ({ useGetProjectSupportStats: (id: string) => mockUseGetProjectSupportStats(id), })); +// Mock useGetProjectCases (avoids pulling in useAsgardeo) +vi.mock("@/api/useGetProjectCases", () => ({ + __esModule: true, + default: () => ({ data: { cases: [] }, isLoading: false }), +})); + +// Mock useGetChatHistory +vi.mock("@/api/useGetChatHistory", () => ({ + useGetChatHistory: () => ({ data: { chatHistory: [] } }), +})); + +// Mock support overview card components so SupportPage renders without full Oxygen UI tree +vi.mock( + "@components/support/support-overview-cards/SupportOverviewCard", + () => ({ + __esModule: true, + default: ({ + title, + children, + }: { + title: string; + children: ReactElement; + }) => ( +
+ {title} + {children} +
+ ), + }), +); +vi.mock( + "@components/support/support-overview-cards/OutstandingCasesList", + () => ({ + __esModule: true, + default: () =>
Cases list
, + }), +); +vi.mock("@components/support/support-overview-cards/ChatHistoryList", () => ({ + __esModule: true, + default: () =>
Chat list
, +})); + beforeEach(() => { vi.clearAllMocks(); }); @@ -218,4 +278,25 @@ describe("SupportPage", () => { expect(screen.getByText("Start New Chat")).toBeInTheDocument(); expect(screen.getAllByTestId("icon-bot")).toHaveLength(2); }); + + it("should render Outstanding Cases and Chat History overview cards when data is loaded", () => { + mockUseGetProjectSupportStats.mockReturnValue({ + isLoading: false, + data: { + totalCases: 10, + activeChats: 5, + sessionChats: 15, + resolvedChats: 20, + }, + }); + + render( + + + , + ); + + expect(screen.getByText("Outstanding Cases")).toBeInTheDocument(); + expect(screen.getByText("Chat History")).toBeInTheDocument(); + }); }); From 3c7e3479c52da76cd5284cb8c7930aaeef8cc11b Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 11:25:12 +0530 Subject: [PATCH 07/20] Add SUPPORT_OVERVIEW_CHAT_LIMIT constant Introduce SUPPORT_OVERVIEW_CHAT_LIMIT (value 5) to control how many chat history items are shown on support overview cards. Mirrors the existing SUPPORT_OVERVIEW_CASES_LIMIT for consistency. --- apps/customer-portal/webapp/src/constants/supportConstants.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/customer-portal/webapp/src/constants/supportConstants.ts b/apps/customer-portal/webapp/src/constants/supportConstants.ts index c6a679753b..15ed252f63 100644 --- a/apps/customer-portal/webapp/src/constants/supportConstants.ts +++ b/apps/customer-portal/webapp/src/constants/supportConstants.ts @@ -66,3 +66,6 @@ export const SUPPORT_STAT_CONFIGS: SupportStatConfig[] = [ /** Number of outstanding cases to show on support overview cards. */ export const SUPPORT_OVERVIEW_CASES_LIMIT = 5; + +/** Number of chat history items to show on support overview cards. */ +export const SUPPORT_OVERVIEW_CHAT_LIMIT = 5; From c09a9c93d80f0f2a95b54772d2949ff4792c6829 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 11:26:07 +0530 Subject: [PATCH 08/20] Expose sx prop and make card full-size Add typed sx support to SupportOverviewCard by importing SxProps and Theme and adding an optional sx?: SxProps prop. Apply sx (spread) to the root Box and set height/width to 100% so the card can expand to its container; also make the content Box flex: 1 and full width. These changes let consumers customize styling via the sx prop and ensure consistent full-size layout. --- .../SupportOverviewCard.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/SupportOverviewCard.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/SupportOverviewCard.tsx index 340ff50e2a..617bfe1fc4 100644 --- a/apps/customer-portal/webapp/src/components/support/support-overview-cards/SupportOverviewCard.tsx +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/SupportOverviewCard.tsx @@ -22,6 +22,8 @@ import { alpha, colors, useTheme, + type SxProps, + type Theme, } from "@wso2/oxygen-ui"; import { ArrowRight } from "@wso2/oxygen-ui-icons-react"; import type { ComponentType } from "react"; @@ -35,6 +37,7 @@ export interface SupportOverviewCardProps { children: React.ReactNode; footerButtonLabel: string; onFooterClick: () => void; + sx?: SxProps; } /** @@ -51,6 +54,7 @@ export default function SupportOverviewCard({ children, footerButtonLabel, onFooterClick, + sx, }: SupportOverviewCardProps): JSX.Element { const theme = useTheme(); const paletteKey = iconVariant === "blue" ? "info" : "warning"; @@ -64,6 +68,9 @@ export default function SupportOverviewCard({ display: "flex", flexDirection: "column", gap: 2, + height: "100%", + width: "100%", + ...sx, }} > @@ -91,7 +98,15 @@ export default function SupportOverviewCard({
- + {children} From 807fda0b20eb28b6f2e703431fa21849d8712929 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 11:26:24 +0530 Subject: [PATCH 09/20] Make outstanding cases skeleton full width Add width: "100%" to the Box container in OutstandingCasesSkeleton.tsx so the skeleton cards span the full available width and avoid layout collapse. This is a small styling tweak to ensure consistent layout of the support overview skeleton items. --- .../support-overview-cards/OutstandingCasesSkeleton.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesSkeleton.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesSkeleton.tsx index 8e9c629771..b0db64feef 100644 --- a/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesSkeleton.tsx +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesSkeleton.tsx @@ -25,7 +25,9 @@ import { type JSX } from "react"; */ export default function OutstandingCasesSkeleton(): JSX.Element { return ( - + {Array.from({ length: SUPPORT_OVERVIEW_CASES_LIMIT }).map((_, index) => ( Date: Mon, 9 Feb 2026 11:28:49 +0530 Subject: [PATCH 10/20] Truncate case titles and set container width Prevent long case titles from breaking the layout in OutstandingCasesList: add width: "100%" to the outer container, wrap the Typography in a Box with minWidth: 0, and apply textOverflow/ellipsis and -webkit-line-clamp styling to limit titles to a single truncated line. --- .../OutstandingCasesList.tsx | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesList.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesList.tsx index fc069d3e51..541094a7c5 100644 --- a/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesList.tsx +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesList.tsx @@ -61,7 +61,9 @@ export default function OutstandingCasesList({ } return ( - + {cases.map((c) => { const colorPath = getStatusColor(c.status?.label); const resolvedColor = resolveColorFromTheme(colorPath, theme); @@ -105,15 +107,21 @@ export default function OutstandingCasesList({ /> - - {c.title} - + + + {c.title} + + From 51068876df67d9c1519f1fa61ce87f689b6de404 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 11:29:04 +0530 Subject: [PATCH 11/20] Add ChatHistorySkeleton component Introduce ChatHistorySkeleton.tsx: a React component that renders a list of loading skeletons for the support chat history using @wso2/oxygen-ui components. It generates placeholders based on SUPPORT_OVERVIEW_CHAT_LIMIT and uses non-interactive Form.CardButton items to visually match the chat list while data is loading. --- .../ChatHistorySkeleton.tsx | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistorySkeleton.tsx diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistorySkeleton.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistorySkeleton.tsx new file mode 100644 index 0000000000..053c8913d3 --- /dev/null +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistorySkeleton.tsx @@ -0,0 +1,81 @@ +// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import { Box, Form, Skeleton } from "@wso2/oxygen-ui"; +import { type JSX } from "react"; +import { SUPPORT_OVERVIEW_CHAT_LIMIT } from "@constants/supportConstants"; + +/** + * Renders a list of loading skeletons for the chat history list. + * + * @returns {JSX.Element} The skeleton list. + */ +export default function ChatHistorySkeleton(): JSX.Element { + return ( + + {Array.from({ length: SUPPORT_OVERVIEW_CHAT_LIMIT }).map((_, index) => ( + + + + + + + + + + + + + + + + + + + + + + + + ))} + + ); +} From c2a74b22412bf643bfa8dfa7dcf53de4c7bd3e79 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 14:10:57 +0530 Subject: [PATCH 12/20] Add ChatHistoryList component Introduce ChatHistoryList React component to render support chat history in the customer portal. The component accepts items, isLoading, and onItemAction props, shows a skeleton when loading, an empty state when no items, and maps each ChatHistoryItem to a clickable card with title, metadata, status chip (theme-resolved color), and an action button (View or Resume). Uses support utility functions (ChatAction, getChatStatusAction/getChatStatusColor/getChatActionColor, resolveColorFromTheme), Oxygen UI components, and a ChatHistorySkeleton. --- .../ChatHistoryList.tsx | 194 ++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx new file mode 100644 index 0000000000..772c45deff --- /dev/null +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx @@ -0,0 +1,194 @@ +// Copyright (c) 2026 WSO2 LLC. (https://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import { + Box, + Button, + Chip, + Form, + Stack, + Typography, + alpha, + useTheme, +} from "@wso2/oxygen-ui"; +import { + Bot, + CircleCheck, + Clock, + ExternalLink, + Play, +} from "@wso2/oxygen-ui-icons-react"; +import type { JSX } from "react"; +import type { ChatHistoryItem } from "@models/responses"; +import { + ChatAction, + getChatActionColor, + getChatStatusAction, + getChatStatusColor, + resolveColorFromTheme, +} from "@utils/support"; +import ChatHistorySkeleton from "@/components/support/support-overview-cards/ChatHistorySkeleton"; + +export interface ChatHistoryListProps { + items: ChatHistoryItem[]; + isLoading?: boolean; + onItemAction?: (chatId: string, action: "view" | "resume") => void; +} + +/** + * Renders a list of chat history rows for the support overview card. + * + * @param {ChatHistoryListProps} props - Items and optional action handler. + * @returns {JSX.Element} The list of chat rows. + */ +export default function ChatHistoryList({ + items, + isLoading, + onItemAction, +}: ChatHistoryListProps): JSX.Element { + const theme = useTheme(); + + if (isLoading) { + return ; + } + + if (items.length === 0) { + return ( + + No chat history. + + ); + } + + return ( + + {items.map((item) => { + const action = getChatStatusAction(item.status); + const StatusIcon = action === ChatAction.VIEW ? CircleCheck : Clock; + const chipColorPath = getChatStatusColor(item.status); + const resolvedColor = resolveColorFromTheme(chipColorPath, theme); + + return ( + onItemAction?.(item.chatId, action)} + sx={{ + p: 2, + display: "flex", + flexDirection: "column", + alignItems: "stretch", + gap: 1, + }} + > + + + + + + + + {item.title} + + + + + + + {item.startedTime} + + + • + + + {item.messages} messages + + + • + + + {item.kbArticles} KB articles + + + + + + + } + /> + + + + ); + })} + + ); +} From 9fe189e1d8bd2779b01d246f91db4d614d848ac5 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 14:11:26 +0530 Subject: [PATCH 13/20] Add chat status helpers & tweak styles Introduce chat status/action utilities in utils/support.ts: add ChatAction constants, ChatAction/ChatStatusAction types, and helper functions getChatStatusAction, getChatActionColor, and getChatStatusColor to centralize mapping of statuses to actions and palette colors. Simplify imports and adjust JSDoc comments. Also remove an explicit borderColor: "transparent" from the OutstandingCasesList chip styling to clean up the component's appearance. --- .../OutstandingCasesList.tsx | 1 - .../webapp/src/utils/support.ts | 63 ++++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesList.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesList.tsx index 541094a7c5..ec9fcef3de 100644 --- a/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesList.tsx +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/OutstandingCasesList.tsx @@ -134,7 +134,6 @@ export default function OutstandingCasesList({ px: 0.5, bgcolor: alpha(resolvedColor, 0.1), color: resolvedColor, - borderColor: "transparent", "& .MuiChip-icon": { color: "inherit", }, diff --git a/apps/customer-portal/webapp/src/utils/support.ts b/apps/customer-portal/webapp/src/utils/support.ts index 3cb02b09fd..f3f8f9f154 100644 --- a/apps/customer-portal/webapp/src/utils/support.ts +++ b/apps/customer-portal/webapp/src/utils/support.ts @@ -14,11 +14,68 @@ // specific language governing permissions and limitations // under the License. -import { colors, type SxProps, type Theme } from "@wso2/oxygen-ui"; +import type { Theme } from "@wso2/oxygen-ui"; + +export const ChatAction = { + VIEW: "view", + RESUME: "resume", +} as const; + +export type ChatAction = (typeof ChatAction)[keyof typeof ChatAction]; + +export type ChatStatusAction = "view" | "resume"; + +/** + * Returns whether to show View or Resume for a chat status. + * + * @param status - Chat status string (e.g. Resolved, Still Open, Abandoned). + * @returns {ChatStatusAction} "view" or "resume". + */ +export function getChatStatusAction(status: string): ChatStatusAction { + const normalized = status?.toLowerCase() || ""; + if (normalized.includes("open")) return ChatAction.RESUME; + return ChatAction.VIEW; +} + +/** + * Returns the color for a chat action button. + * + * @param action - The action type ("view" or "resume"). + * @returns {string} Palette color path. + */ +export function getChatActionColor( + action: ChatStatusAction, +): "primary" | "info" | "success" | "warning" | "error" { + if (action === ChatAction.RESUME) { + return "info"; + } + return "primary"; +} + +/** + * Returns the color path for a chat status. + * + * @param status - Chat status string. + * @returns {string} Palette color path. + */ +export function getChatStatusColor(status: string): string { + const normalized = status?.toLowerCase() || ""; + if (normalized.includes("resolved")) { + return "success.main"; + } + if (normalized.includes("open")) { + return "info.main"; + } + if (normalized.includes("abandoned")) { + return "error.main"; + } + return "secondary.main"; +} + /** * Resolves a color from the theme palette for the alpha() utility. * - * @param path - Color path (e.g., 'primary.main'). + * @param path - Color path. * @param theme - Oxygen UI theme. * @returns {string} The resolved color string. */ @@ -37,7 +94,7 @@ export function resolveColorFromTheme(path: string, theme: Theme): string { * Formats a date string or Date object into a relative time (e.g., "2 days ago", "1 hour ago"). * * @param date - Date string or Date object. - * @returns {string} Human-readable relative time. + * @returns {string} Human readable relative time. */ export function formatRelativeTime(date: string | Date | undefined): string { if (!date) return "--"; From b060787dbcef8209d87cc6495efcc84698fec2eb Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 14:11:39 +0530 Subject: [PATCH 14/20] Update tests to use getChatStatusColor Replace assertions for getChatStatusChipSx/getChatStatusRowSx with tests for the new getChatStatusColor helper in support utils. Tests now verify returned theme color tokens: Resolved => success.main, Still Open => info.main, Abandoned => error.main, and fallback => secondary.main, reflecting a refactor to return color keys instead of full sx objects. --- .../src/utils/__tests__/support.test.ts | 35 ++++++------------- 1 file changed, 10 insertions(+), 25 deletions(-) diff --git a/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts b/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts index 02633ccd50..81cb2891b4 100644 --- a/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts +++ b/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts @@ -17,8 +17,7 @@ import { describe, expect, it } from "vitest"; import { getChatStatusAction, - getChatStatusChipSx, - getChatStatusRowSx, + getChatStatusColor, formatRelativeTime, } from "@utils/support"; @@ -36,35 +35,21 @@ describe("support utils", () => { }); }); - describe("getChatStatusChipSx", () => { - it("should return green sx for Resolved", () => { - const sx = getChatStatusChipSx("Resolved") as any; - expect(sx.bgcolor).toBeDefined(); - expect(sx.color).toBeDefined(); - expect(sx.borderColor).toBeDefined(); + describe("getChatStatusColor", () => { + it("should return success.main for Resolved", () => { + expect(getChatStatusColor("Resolved")).toBe("success.main"); }); - it("should return blue sx for Still Open", () => { - const sx = getChatStatusChipSx("Still Open") as any; - expect(sx.bgcolor).toBeDefined(); - expect(sx.color).toBeDefined(); + it("should return info.main for Still Open", () => { + expect(getChatStatusColor("Still Open")).toBe("info.main"); }); - it("should return grey sx for Abandoned", () => { - const sx = getChatStatusChipSx("Abandoned") as any; - expect(sx.bgcolor).toBeDefined(); - }); - }); - - describe("getChatStatusRowSx", () => { - it("should return green tint for Resolved", () => { - const sx = getChatStatusRowSx("Resolved") as any; - expect(sx.bgcolor).toBeDefined(); + it("should return error.main for Abandoned", () => { + expect(getChatStatusColor("Abandoned")).toBe("error.main"); }); - it("should return blue tint for Still Open", () => { - const sx = getChatStatusRowSx("Still Open") as any; - expect(sx.bgcolor).toBeDefined(); + it("should return secondary.main for others", () => { + expect(getChatStatusColor("")).toBe("secondary.main"); }); }); From fbe4a1250aeabf1259a52f7d6f8d8ab5524a2e76 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 14:28:17 +0530 Subject: [PATCH 15/20] Fix imports and pluralization in customer portal Replace absolute/alias imports with relative paths to ensure correct module resolution in runtime and tests (App.tsx, AppWithConfig.tsx, main.tsx, AuthGuard.test.tsx). Also improve ChatHistoryList copy by pluralizing "message(s)" and "KB article(s)" correctly based on count. --- apps/customer-portal/webapp/src/App.tsx | 2 +- apps/customer-portal/webapp/src/AppWithConfig.tsx | 2 +- .../customer-portal/webapp/src/__tests__/AuthGuard.test.tsx | 2 +- .../support/support-overview-cards/ChatHistoryList.tsx | 6 ++++-- apps/customer-portal/webapp/src/main.tsx | 2 +- 5 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/customer-portal/webapp/src/App.tsx b/apps/customer-portal/webapp/src/App.tsx index e32793dead..750fc9bdac 100644 --- a/apps/customer-portal/webapp/src/App.tsx +++ b/apps/customer-portal/webapp/src/App.tsx @@ -26,7 +26,7 @@ import CreateCasePage from "@pages/CreateCasePage"; import AppLayout from "@layouts/AppLayout"; import { LoaderProvider } from "@context/linear-loader/LoaderContext"; import LoginPage from "@pages/LoginPage"; -import AuthGuard from "@/AuthGuard"; +import AuthGuard from "./AuthGuard"; export default function App(): JSX.Element { return ( diff --git a/apps/customer-portal/webapp/src/AppWithConfig.tsx b/apps/customer-portal/webapp/src/AppWithConfig.tsx index a78e0bc510..cfa630cf3e 100644 --- a/apps/customer-portal/webapp/src/AppWithConfig.tsx +++ b/apps/customer-portal/webapp/src/AppWithConfig.tsx @@ -18,7 +18,7 @@ import type { JSX } from "react"; import { OxygenUIThemeProvider } from "@wso2/oxygen-ui"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; -import App from "@/App"; +import App from "./App"; import { AsgardeoProvider } from "@asgardeo/react"; import { themeConfig } from "@config/themeConfig"; import { loggerConfig } from "@config/loggerConfig"; diff --git a/apps/customer-portal/webapp/src/__tests__/AuthGuard.test.tsx b/apps/customer-portal/webapp/src/__tests__/AuthGuard.test.tsx index bc19cfd600..3b0b29c1bf 100644 --- a/apps/customer-portal/webapp/src/__tests__/AuthGuard.test.tsx +++ b/apps/customer-portal/webapp/src/__tests__/AuthGuard.test.tsx @@ -17,7 +17,7 @@ import { render, screen } from "@testing-library/react"; import { useAsgardeo } from "@asgardeo/react"; import { MemoryRouter, Route, Routes } from "react-router"; -import AuthGuard from "@/AuthGuard"; // Corrected path +import AuthGuard from "../AuthGuard"; import { describe, it, expect, vi, beforeEach } from "vitest"; import React from "react"; import { useLoader } from "@context/linear-loader/LoaderContext"; diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx index 772c45deff..1a1ebdc444 100644 --- a/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx @@ -133,13 +133,15 @@ export default function ChatHistoryList({ • - {item.messages} messages + {item.messages}{" "} + {item.messages === 1 ? "message" : "messages"} • - {item.kbArticles} KB articles + {item.kbArticles}{" "} + {item.kbArticles === 1 ? "KB article" : "KB articles"} diff --git a/apps/customer-portal/webapp/src/main.tsx b/apps/customer-portal/webapp/src/main.tsx index ed05c2f99f..4d9280f271 100644 --- a/apps/customer-portal/webapp/src/main.tsx +++ b/apps/customer-portal/webapp/src/main.tsx @@ -16,7 +16,7 @@ import React from "react"; import { createRoot } from "react-dom/client"; -import AppWithConfig from "@/AppWithConfig"; +import AppWithConfig from "./AppWithConfig"; createRoot(document.getElementById("root")!).render( From d4bf1cb8d18eb2b25d87e61d66a982f8b793928c Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 14:42:11 +0530 Subject: [PATCH 16/20] SupportOverviewCard: accept array sx; update tests Change Paper's sx prop to an array so caller sx (object or array) is merged safely into the base styles. Update SupportPage tests: remove unnecessary React import (use modern JSX runtime type import), add a mock for Stack to prevent missing-component test failures, and normalize/mock paths for useGetProjectCases and useGetChatHistory. These changes fix styling composition and stabilize tests against component/mocking changes. --- .../SupportOverviewCard.tsx | 20 ++++++++++--------- .../src/pages/__tests__/SupportPage.test.tsx | 11 +++++++--- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/SupportOverviewCard.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/SupportOverviewCard.tsx index 617bfe1fc4..44ef8c76d0 100644 --- a/apps/customer-portal/webapp/src/components/support/support-overview-cards/SupportOverviewCard.tsx +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/SupportOverviewCard.tsx @@ -63,15 +63,17 @@ export default function SupportOverviewCard({ return ( ({ // Mock @wso2/oxygen-ui components vi.mock("@wso2/oxygen-ui", () => ({ Box: ({ children }: any) =>
{children}
, + Stack: ({ children, spacing }: any) => ( +
+ {children} +
+ ), Grid: ({ children, container, spacing, size, sx }: any) => (
({ })); // Mock useGetProjectCases (avoids pulling in useAsgardeo) -vi.mock("@/api/useGetProjectCases", () => ({ +vi.mock("@api/useGetProjectCases", () => ({ __esModule: true, default: () => ({ data: { cases: [] }, isLoading: false }), })); // Mock useGetChatHistory -vi.mock("@/api/useGetChatHistory", () => ({ +vi.mock("@api/useGetChatHistory", () => ({ useGetChatHistory: () => ({ data: { chatHistory: [] } }), })); From e62a34f2a8713cb6a5170e7f46afe4b4929fed2b Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 14:43:37 +0530 Subject: [PATCH 17/20] Add ChatActionState type and use in getChatActionColor Introduce ChatActionState union type ('primary' | 'info' | 'success' | 'warning' | 'error') and update getChatActionColor to return ChatActionState instead of an inline union. Centralizes action color states for reuse and improves type consistency. --- apps/customer-portal/webapp/src/utils/support.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/customer-portal/webapp/src/utils/support.ts b/apps/customer-portal/webapp/src/utils/support.ts index f3f8f9f154..3407c73f76 100644 --- a/apps/customer-portal/webapp/src/utils/support.ts +++ b/apps/customer-portal/webapp/src/utils/support.ts @@ -23,6 +23,13 @@ export const ChatAction = { export type ChatAction = (typeof ChatAction)[keyof typeof ChatAction]; +export type ChatActionState = + | "primary" + | "info" + | "success" + | "warning" + | "error"; + export type ChatStatusAction = "view" | "resume"; /** @@ -43,9 +50,7 @@ export function getChatStatusAction(status: string): ChatStatusAction { * @param action - The action type ("view" or "resume"). * @returns {string} Palette color path. */ -export function getChatActionColor( - action: ChatStatusAction, -): "primary" | "info" | "success" | "warning" | "error" { +export function getChatActionColor(action: ChatStatusAction): ChatActionState { if (action === ChatAction.RESUME) { return "info"; } From 180241fd09774842e65a7532442f6100bd7e8859 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 14:58:37 +0530 Subject: [PATCH 18/20] Switch to Card components and add Chat types Replace custom Form.CardButton/CardContent/CardActions with MUI Card/CardContent/CardActions in chat history list, add hover/cursor styles, and compute chip colors via theme function. Extract ChatAction and ChatStatus constants/types to supportConstants and update support utils to use them (including getChatStatusAction/getChatActionColor/getChatStatusColor). Update tests to reference the new constants and validate action/color behavior. --- .../ChatHistoryList.tsx | 34 ++++++++++-------- .../webapp/src/constants/supportConstants.ts | 17 +++++++++ .../src/utils/__tests__/support.test.ts | 36 +++++++++++++------ .../webapp/src/utils/support.ts | 30 ++++++++-------- 4 files changed, 76 insertions(+), 41 deletions(-) diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx index 1a1ebdc444..c39ca8ed5c 100644 --- a/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx @@ -17,12 +17,14 @@ import { Box, Button, + Card, + CardActions, + CardContent, Chip, - Form, Stack, Typography, alpha, - useTheme, + type Theme, } from "@wso2/oxygen-ui"; import { Bot, @@ -33,8 +35,8 @@ import { } from "@wso2/oxygen-ui-icons-react"; import type { JSX } from "react"; import type { ChatHistoryItem } from "@models/responses"; +import { ChatAction } from "@constants/supportConstants"; import { - ChatAction, getChatActionColor, getChatStatusAction, getChatStatusColor, @@ -45,7 +47,7 @@ import ChatHistorySkeleton from "@/components/support/support-overview-cards/Cha export interface ChatHistoryListProps { items: ChatHistoryItem[]; isLoading?: boolean; - onItemAction?: (chatId: string, action: "view" | "resume") => void; + onItemAction?: (chatId: string, action: ChatAction) => void; } /** @@ -59,8 +61,6 @@ export default function ChatHistoryList({ isLoading, onItemAction, }: ChatHistoryListProps): JSX.Element { - const theme = useTheme(); - if (isLoading) { return ; } @@ -81,10 +81,9 @@ export default function ChatHistoryList({ const action = getChatStatusAction(item.status); const StatusIcon = action === ChatAction.VIEW ? CircleCheck : Clock; const chipColorPath = getChatStatusColor(item.status); - const resolvedColor = resolveColorFromTheme(chipColorPath, theme); return ( - onItemAction?.(item.chatId, action)} sx={{ @@ -93,9 +92,13 @@ export default function ChatHistoryList({ flexDirection: "column", alignItems: "stretch", gap: 1, + cursor: "pointer", + "&:hover": { + bgcolor: "action.hover", + }, }} > - - + - + + alpha(resolveColorFromTheme(chipColorPath, theme), 0.1), + color: chipColorPath, "& .MuiChip-icon": { color: "inherit", }, @@ -187,8 +191,8 @@ export default function ChatHistoryList({ > {action === ChatAction.VIEW ? "View" : "Resume"} - - + + ); })} diff --git a/apps/customer-portal/webapp/src/constants/supportConstants.ts b/apps/customer-portal/webapp/src/constants/supportConstants.ts index 811b55a646..64e3821cef 100644 --- a/apps/customer-portal/webapp/src/constants/supportConstants.ts +++ b/apps/customer-portal/webapp/src/constants/supportConstants.ts @@ -25,6 +25,23 @@ import { import { type ComponentType } from "react"; import type { ProjectSupportStats } from "@models/responses"; +// Chat actions for the history list. +export const ChatAction = { + VIEW: "view", + RESUME: "resume", +} as const; + +export type ChatAction = (typeof ChatAction)[keyof typeof ChatAction]; + +// Chat status types. +export const ChatStatus = { + RESOLVED: "Resolved", + STILL_OPEN: "Still Open", + ABANDONED: "Abandoned", +} as const; + +export type ChatStatus = (typeof ChatStatus)[keyof typeof ChatStatus]; + // Interface for support statistics card configuration. export interface SupportStatConfig { iconColor: "primary" | "secondary" | "success" | "error" | "info" | "warning"; diff --git a/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts b/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts index 81cb2891b4..eb36c393fd 100644 --- a/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts +++ b/apps/customer-portal/webapp/src/utils/__tests__/support.test.ts @@ -15,37 +15,53 @@ // under the License. import { describe, expect, it } from "vitest"; +import { ChatAction, ChatStatus } from "@constants/supportConstants"; import { getChatStatusAction, getChatStatusColor, + getChatActionColor, formatRelativeTime, } from "@utils/support"; describe("support utils", () => { describe("getChatStatusAction", () => { - it("should return 'resume' for Still Open", () => { - expect(getChatStatusAction("Still Open")).toBe("resume"); - expect(getChatStatusAction("still open")).toBe("resume"); + it("should return resume for Still Open", () => { + expect(getChatStatusAction(ChatStatus.STILL_OPEN)).toBe( + ChatAction.RESUME, + ); + expect(getChatStatusAction(ChatStatus.STILL_OPEN.toLowerCase())).toBe( + ChatAction.RESUME, + ); }); - it("should return 'view' for Resolved and Abandoned", () => { - expect(getChatStatusAction("Resolved")).toBe("view"); - expect(getChatStatusAction("Abandoned")).toBe("view"); - expect(getChatStatusAction("")).toBe("view"); + it("should return view for Resolved and Abandoned", () => { + expect(getChatStatusAction(ChatStatus.RESOLVED)).toBe(ChatAction.VIEW); + expect(getChatStatusAction(ChatStatus.ABANDONED)).toBe(ChatAction.VIEW); + expect(getChatStatusAction("")).toBe(ChatAction.VIEW); + }); + }); + + describe("getChatActionColor", () => { + it("should return info for RESUME", () => { + expect(getChatActionColor(ChatAction.RESUME)).toBe("info"); + }); + + it("should return primary for VIEW", () => { + expect(getChatActionColor(ChatAction.VIEW)).toBe("primary"); }); }); describe("getChatStatusColor", () => { it("should return success.main for Resolved", () => { - expect(getChatStatusColor("Resolved")).toBe("success.main"); + expect(getChatStatusColor(ChatStatus.RESOLVED)).toBe("success.main"); }); it("should return info.main for Still Open", () => { - expect(getChatStatusColor("Still Open")).toBe("info.main"); + expect(getChatStatusColor(ChatStatus.STILL_OPEN)).toBe("info.main"); }); it("should return error.main for Abandoned", () => { - expect(getChatStatusColor("Abandoned")).toBe("error.main"); + expect(getChatStatusColor(ChatStatus.ABANDONED)).toBe("error.main"); }); it("should return secondary.main for others", () => { diff --git a/apps/customer-portal/webapp/src/utils/support.ts b/apps/customer-portal/webapp/src/utils/support.ts index 3407c73f76..b087137d73 100644 --- a/apps/customer-portal/webapp/src/utils/support.ts +++ b/apps/customer-portal/webapp/src/utils/support.ts @@ -14,15 +14,9 @@ // specific language governing permissions and limitations // under the License. +import { ChatAction, ChatStatus } from "@constants/supportConstants"; import type { Theme } from "@wso2/oxygen-ui"; -export const ChatAction = { - VIEW: "view", - RESUME: "resume", -} as const; - -export type ChatAction = (typeof ChatAction)[keyof typeof ChatAction]; - export type ChatActionState = | "primary" | "info" @@ -30,15 +24,13 @@ export type ChatActionState = | "warning" | "error"; -export type ChatStatusAction = "view" | "resume"; - /** * Returns whether to show View or Resume for a chat status. * * @param status - Chat status string (e.g. Resolved, Still Open, Abandoned). - * @returns {ChatStatusAction} "view" or "resume". + * @returns {ChatActionType} "view" or "resume". */ -export function getChatStatusAction(status: string): ChatStatusAction { +export function getChatStatusAction(status: string): ChatAction { const normalized = status?.toLowerCase() || ""; if (normalized.includes("open")) return ChatAction.RESUME; return ChatAction.VIEW; @@ -48,9 +40,9 @@ export function getChatStatusAction(status: string): ChatStatusAction { * Returns the color for a chat action button. * * @param action - The action type ("view" or "resume"). - * @returns {string} Palette color path. + * @returns {ChatActionState} Palette color path. */ -export function getChatActionColor(action: ChatStatusAction): ChatActionState { +export function getChatActionColor(action: ChatAction): ChatActionState { if (action === ChatAction.RESUME) { return "info"; } @@ -63,15 +55,21 @@ export function getChatActionColor(action: ChatStatusAction): ChatActionState { * @param status - Chat status string. * @returns {string} Palette color path. */ +/** + * Returns the color for a chat status chip. + * + * @param status - Chat status string (e.g. Resolved, Still Open, Abandoned). + * @returns {string} Palette color path. + */ export function getChatStatusColor(status: string): string { const normalized = status?.toLowerCase() || ""; - if (normalized.includes("resolved")) { + if (normalized.includes(ChatStatus.RESOLVED)) { return "success.main"; } - if (normalized.includes("open")) { + if (normalized.includes(ChatStatus.STILL_OPEN)) { return "info.main"; } - if (normalized.includes("abandoned")) { + if (normalized.includes(ChatStatus.ABANDONED)) { return "error.main"; } return "secondary.main"; From 0e23e4267fce0f88bd7cf83101cf4056f4db4aa3 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 15:01:33 +0530 Subject: [PATCH 19/20] Lowercase ChatStatus checks in getChatStatusColor Ensure comparisons use lowercase ChatStatus values when checking a normalized status string. Previously the code compared a lowercased input against the enum constants directly (mismatch), so each ChatStatus is now lowercased before calling includes(), fixing status-to-color mapping for resolved, still_open, and abandoned states. --- apps/customer-portal/webapp/src/utils/support.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/customer-portal/webapp/src/utils/support.ts b/apps/customer-portal/webapp/src/utils/support.ts index b087137d73..77d46fcc24 100644 --- a/apps/customer-portal/webapp/src/utils/support.ts +++ b/apps/customer-portal/webapp/src/utils/support.ts @@ -63,13 +63,13 @@ export function getChatActionColor(action: ChatAction): ChatActionState { */ export function getChatStatusColor(status: string): string { const normalized = status?.toLowerCase() || ""; - if (normalized.includes(ChatStatus.RESOLVED)) { + if (normalized.includes(ChatStatus.RESOLVED.toLowerCase())) { return "success.main"; } - if (normalized.includes(ChatStatus.STILL_OPEN)) { + if (normalized.includes(ChatStatus.STILL_OPEN.toLowerCase())) { return "info.main"; } - if (normalized.includes(ChatStatus.ABANDONED)) { + if (normalized.includes(ChatStatus.ABANDONED.toLowerCase())) { return "error.main"; } return "secondary.main"; From e8ee2b81110ab15b9c9620a3a208923fe923ca40 Mon Sep 17 00:00:00 2001 From: "K.Dileepa Thushan Peiris" Date: Mon, 9 Feb 2026 15:06:43 +0530 Subject: [PATCH 20/20] Use Form.CardButton instead of Card in chat list Replace the Material-UI Card wrapper with the Form.CardButton component for chat history items. Removed the Card import, added Form to the MUI import list and imported CardButton from @wso2/oxygen-ui. Updated JSX to render (preserving sx and onClick) instead of to use the card-as-button component. --- .../support/support-overview-cards/ChatHistoryList.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx b/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx index c39ca8ed5c..a09cb68ddb 100644 --- a/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx +++ b/apps/customer-portal/webapp/src/components/support/support-overview-cards/ChatHistoryList.tsx @@ -17,10 +17,10 @@ import { Box, Button, - Card, CardActions, CardContent, Chip, + Form, Stack, Typography, alpha, @@ -43,6 +43,7 @@ import { resolveColorFromTheme, } from "@utils/support"; import ChatHistorySkeleton from "@/components/support/support-overview-cards/ChatHistorySkeleton"; +import { CardButton } from "@wso2/oxygen-ui/components/Form/CardButton"; export interface ChatHistoryListProps { items: ChatHistoryItem[]; @@ -83,7 +84,7 @@ export default function ChatHistoryList({ const chipColorPath = getChatStatusColor(item.status); return ( - onItemAction?.(item.chatId, action)} sx={{ @@ -192,7 +193,7 @@ export default function ChatHistoryList({ {action === ChatAction.VIEW ? "View" : "Resume"} - + ); })}