-
Notifications
You must be signed in to change notification settings - Fork 49
[Customer Portal][FE][Web] Implement Project Details Page and Modular Overview Components #88
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Rashmika998
merged 78 commits into
wso2-open-operations:customer-portal-milestone-1
from
dileepapeiris:feat/add-project-overview-page
Feb 4, 2026
Merged
Changes from all commits
Commits
Show all changes
78 commits
Select commit
Hold shift + click to select a range
e86be54
Add TabBar unit tests
dileepapeiris 9517f85
Add TabBar component
dileepapeiris e39b2d9
Add project details tab constants
dileepapeiris 3ac9473
Render ProjectDetails for project-details route
dileepapeiris b5a9610
Add ProjectDetails page with tabs
dileepapeiris 150cdd8
Add tests for useGetProjectDetails hook
dileepapeiris 8c74949
Add tests for useGetProjectStat hook
dileepapeiris c2b3603
Add useGetProjectDetails React Query hook
dileepapeiris d31566d
Add tests for projectStats utils
dileepapeiris 9685fa1
Add projectStats utils for dates & chip colors
dileepapeiris 35a47af
Render project overview with data and loader
dileepapeiris 2353f6f
Use project details in CreateCasePage
dileepapeiris 0b6ce7c
CreateCasePage tests: mock project hook/icons
dileepapeiris f3856c6
Add useGetProjectStat hook for project stats
dileepapeiris 08c04ed
Active filters: use warning color; drop prop
dileepapeiris b657812
Add project details and stats interfaces
dileepapeiris 947d4c2
Add tests for ProjectName component
dileepapeiris 2321a5b
Add tests for ProjectHeader
dileepapeiris 0fe28a9
Add ProjectInformationCard unit tests
dileepapeiris effdcaf
Add tests for ProjectMetadata component
dileepapeiris 0b75e8a
Add tests for ProjectDescription component
dileepapeiris 891074f
Add ContactInfoCard unit tests
dileepapeiris cb2c4a1
Add ContactRow unit tests
dileepapeiris 4bc13df
Add ProjectMetadata component
dileepapeiris a3554f3
Enhance CaseDetailsSection tests and mocks
dileepapeiris 14507de
Make basic info fields read-only with edit mode
dileepapeiris 2579789
Use outlined variant for AI Generated chip
dileepapeiris 80de4c0
Add RecentActivityCard component
dileepapeiris a4abcc9
Add getMockProjectStats mock and import
dileepapeiris 05c9fbd
Add mockProjectDetails with subscription data
dileepapeiris db2bcd0
Pass sidebar state to Outlet via context
dileepapeiris 39386fd
Memoize loader show/hide handlers with useCallback
dileepapeiris ac17843
Mock loader context and add loader tests
dileepapeiris 8619fea
Add project details constants and helpers
dileepapeiris 535632c
Add ContactRow component for contacts
dileepapeiris fb6d233
Add RecentActivityCard unit tests
dileepapeiris 1985e8b
Remove projectDetailsConstants.ts
dileepapeiris d437c88
Add ProjectName component
dileepapeiris a3ba21a
Add edit toggle to CaseDetailsSection
dileepapeiris 2dc6b46
Add project-related API query keys
dileepapeiris 6593525
Use primary color for Create Case button
dileepapeiris d6e94be
Use shared Message type in ChatMessageList
dileepapeiris 188c60b
Import shared Message type
dileepapeiris 201c4c0
Update BasicInformationSection tests
dileepapeiris 035ae65
Add ProjectStatisticsCard component
dileepapeiris 9c4271c
Add tests for ProjectStatisticsCard
dileepapeiris 4a3fb69
Add SubscriptionDetails component
dileepapeiris b75ce61
Add ProjectInformationCard component
dileepapeiris 4e4eb2d
Add ProjectHeader component
dileepapeiris abe4ce5
Add ProjectDescription component
dileepapeiris 352822e
Add SubscriptionDetails component tests
dileepapeiris c1343c4
Add ContactInfoCard component
dileepapeiris d5f6e73
Calculate subscription progress and adjust UI
dileepapeiris b585705
Add calculateProgress util to projectStats
dileepapeiris 7c83622
Add calculateProgress tests to projectStats
dileepapeiris 438d4ca
Log project/stats errors in useEffect
dileepapeiris 92750fd
Update SubscriptionDetails test progress mock
dileepapeiris 20f64a4
Count loader usages to avoid premature hide
dileepapeiris b68fcb0
Add ref-counting test for LoaderContext
dileepapeiris dde934c
Add ProjectDetails unit tests
dileepapeiris 656c6fc
Add aria-label to edit icon
dileepapeiris 272a853
Set project metadata labels to medium weight
dileepapeiris 49ee7b2
Refactor loading effect and update test mock
dileepapeiris c51a855
Add test to hide loader after loading
dileepapeiris 7cabe9e
Remove redundant projectId prop
dileepapeiris d2ae486
Merge branch 'customer-portal-milestone-1' into feat/add-project-over…
Rashmika998 a23380e
Use SubscriptionStatus enum in subscription utils
dileepapeiris cca40fb
Add SubscriptionStatus enum and type
dileepapeiris 730c9e2
Use SubscriptionStatus enum for status checks
dileepapeiris 6612b30
Use SubscriptionStatus enum in tests
dileepapeiris d201b8d
Remove outdated comment in SubscriptionDetails test
dileepapeiris 435123b
Use project detail constants
dileepapeiris f6c7899
Remove SubscriptionStatus enum/type
dileepapeiris 11585e2
Use project/case constants in mockData
dileepapeiris 524b1f7
Add project-related constants and types
dileepapeiris e185aef
Use SUBSCRIPTION_STATUS constant
dileepapeiris 1dc1296
Use SUBSCRIPTION_STATUS constant in tests
dileepapeiris 97b5599
Remove unused props from Header and Card mock
dileepapeiris File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 92 additions & 0 deletions
92
apps/customer-portal/webapp/src/api/__tests__/useGetProjectDetails.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| // 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 { renderHook, waitFor } from "@testing-library/react"; | ||
| import { describe, expect, it, vi, beforeEach } from "vitest"; | ||
| import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; | ||
| import useGetProjectDetails from "@/api/useGetProjectDetails"; | ||
| import { mockProjectDetails } from "@/models/mockData"; | ||
| import type { ReactNode } from "react"; | ||
|
|
||
| vi.mock("@/constants/apiConstants", async (importOriginal) => { | ||
| const actual = (await importOriginal()) as any; | ||
| return { | ||
| ...actual, | ||
| API_MOCK_DELAY: 0, | ||
| }; | ||
| }); | ||
|
|
||
| // Mock useLogger | ||
| vi.mock("@/hooks/useLogger", () => ({ | ||
| useLogger: () => ({ | ||
| debug: vi.fn(), | ||
| info: vi.fn(), | ||
| warn: vi.fn(), | ||
| error: vi.fn(), | ||
| }), | ||
| })); | ||
|
|
||
| const createWrapper = () => { | ||
| const queryClient = new QueryClient({ | ||
| defaultOptions: { | ||
| queries: { | ||
| retry: false, | ||
| }, | ||
| }, | ||
| }); | ||
| return ({ children }: { children: ReactNode }) => ( | ||
| <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> | ||
| ); | ||
| }; | ||
|
|
||
| describe("useGetProjectDetails", () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| it("should return project details for a valid project ID", async () => { | ||
| const validProjectId = mockProjectDetails[0].id; | ||
|
|
||
| const { result } = renderHook(() => useGetProjectDetails(validProjectId), { | ||
| wrapper: createWrapper(), | ||
| }); | ||
|
|
||
| await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
|
|
||
| const response = result.current.data; | ||
| expect(response).toBeDefined(); | ||
| expect(response?.id).toBe(validProjectId); | ||
| expect(response?.name).toBe(mockProjectDetails[0].name); | ||
| }); | ||
|
|
||
| it("should error for an invalid project ID", async () => { | ||
| const invalidProjectId = "invalid-id"; | ||
|
|
||
| const { result } = renderHook( | ||
| () => useGetProjectDetails(invalidProjectId), | ||
| { | ||
| wrapper: createWrapper(), | ||
| }, | ||
| ); | ||
|
|
||
| await waitFor(() => expect(result.current.isError).toBe(true)); | ||
|
|
||
| expect(result.current.error).toBeDefined(); | ||
| expect(result.current.error?.message).toContain( | ||
| "Project with ID invalid-id not found", | ||
| ); | ||
| }); | ||
| }); |
89 changes: 89 additions & 0 deletions
89
apps/customer-portal/webapp/src/api/__tests__/useGetProjectStat.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| // 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 { renderHook, waitFor } from "@testing-library/react"; | ||
| import { describe, expect, it, vi, beforeEach } from "vitest"; | ||
| import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; | ||
| import { useGetProjectStat } from "@/api/useGetProjectStat"; | ||
| import { mockProjects } from "@/models/mockData"; | ||
| import type { ReactNode } from "react"; | ||
|
|
||
| vi.mock("@/constants/apiConstants", async (importOriginal) => { | ||
| const actual = (await importOriginal()) as any; | ||
| return { | ||
| ...actual, | ||
| API_MOCK_DELAY: 0, | ||
| }; | ||
| }); | ||
|
|
||
| // Mock useLogger | ||
| vi.mock("@/hooks/useLogger", () => ({ | ||
| useLogger: () => ({ | ||
| debug: vi.fn(), | ||
| info: vi.fn(), | ||
| warn: vi.fn(), | ||
| error: vi.fn(), | ||
| }), | ||
| })); | ||
|
|
||
| const createWrapper = () => { | ||
| const queryClient = new QueryClient({ | ||
| defaultOptions: { | ||
| queries: { | ||
| retry: false, | ||
| }, | ||
| }, | ||
| }); | ||
| return ({ children }: { children: ReactNode }) => ( | ||
| <QueryClientProvider client={queryClient}>{children}</QueryClientProvider> | ||
| ); | ||
| }; | ||
|
|
||
| describe("useGetProjectStat", () => { | ||
| beforeEach(() => { | ||
| vi.clearAllMocks(); | ||
| }); | ||
|
|
||
| it("should return project stats for a valid project ID", async () => { | ||
| const validProjectId = mockProjects[0].id; | ||
|
|
||
| const { result } = renderHook(() => useGetProjectStat(validProjectId), { | ||
| wrapper: createWrapper(), | ||
| }); | ||
|
|
||
| await waitFor(() => expect(result.current.isSuccess).toBe(true)); | ||
|
|
||
| const response = result.current.data; | ||
| expect(response).toBeDefined(); | ||
| expect(response?.projectStats).toBeDefined(); | ||
| expect(response?.recentActivity).toBeDefined(); | ||
| }); | ||
|
|
||
| it("should error for an invalid project ID", async () => { | ||
| const invalidProjectId = "invalid-id"; | ||
|
|
||
| const { result } = renderHook(() => useGetProjectStat(invalidProjectId), { | ||
| wrapper: createWrapper(), | ||
| }); | ||
|
|
||
| await waitFor(() => expect(result.current.isError).toBe(true)); | ||
|
|
||
| expect(result.current.error).toBeDefined(); | ||
| expect(result.current.error?.message).toContain( | ||
| "Project stats not found for ID: invalid-id", | ||
| ); | ||
| }); | ||
| }); |
60 changes: 60 additions & 0 deletions
60
apps/customer-portal/webapp/src/api/useGetProjectDetails.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| // 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 { useQuery, type UseQueryResult } from "@tanstack/react-query"; | ||
| import { useLogger } from "@/hooks/useLogger"; | ||
| import { mockProjectDetails } from "@/models/mockData"; | ||
| import { ApiQueryKeys, API_MOCK_DELAY } from "@/constants/apiConstants"; | ||
| import type { ProjectDetails } from "@/models/responses"; | ||
|
|
||
| /** | ||
| * Custom hook to fetch detailed project information by ID. | ||
| * | ||
| * @param {string} projectId - The ID of the project to fetch details for. | ||
| * @returns {UseQueryResult<ProjectDetails, Error>} The query result object. | ||
| */ | ||
| export default function useGetProjectDetails( | ||
| projectId: string, | ||
| ): UseQueryResult<ProjectDetails, Error> { | ||
| const logger = useLogger(); | ||
|
|
||
| return useQuery<ProjectDetails, Error>({ | ||
| queryKey: [ApiQueryKeys.PROJECT_DETAILS, projectId], | ||
| queryFn: async (): Promise<ProjectDetails> => { | ||
| logger.debug(`Fetching project details for project ID: ${projectId}`); | ||
|
|
||
| // Mock behavior: simulate network latency. | ||
| await new Promise((resolve) => setTimeout(resolve, API_MOCK_DELAY)); | ||
|
|
||
| // Find project by ID from mock data. | ||
| const project = mockProjectDetails.find((p) => p.id === projectId); | ||
|
|
||
| if (!project) { | ||
| logger.error(`Project not found for ID: ${projectId}`); | ||
| throw new Error(`Project with ID ${projectId} not found`); | ||
| } | ||
|
|
||
| logger.debug( | ||
| `Project details fetched successfully for project ID: ${projectId}`, | ||
| project, | ||
| ); | ||
|
|
||
| return project; | ||
| }, | ||
| enabled: !!projectId, | ||
| staleTime: 5 * 60 * 1000, // 5 minutes | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| // 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 { useQuery, type UseQueryResult } from "@tanstack/react-query"; | ||
| import { getMockProjectStats } from "@/models/mockFunctions"; | ||
| import { mockProjects } from "@/models/mockData"; | ||
| import { useLogger } from "@/hooks/useLogger"; | ||
| import { ApiQueryKeys, API_MOCK_DELAY } from "@/constants/apiConstants"; | ||
| import type { ProjectStatsResponse } from "@/models/responses"; | ||
|
|
||
| /** | ||
| * Custom hook to fetch project statistics. | ||
| * | ||
| * @param {string} projectId - The ID of the project. | ||
| * @returns {UseQueryResult<ProjectStatsResponse, Error>} The query result object. | ||
| */ | ||
| export function useGetProjectStat( | ||
| projectId: string, | ||
| ): UseQueryResult<ProjectStatsResponse, Error> { | ||
| const logger = useLogger(); | ||
|
|
||
| return useQuery<ProjectStatsResponse, Error>({ | ||
| queryKey: [ApiQueryKeys.PROJECT_STATS, projectId], | ||
| queryFn: async (): Promise<ProjectStatsResponse> => { | ||
| logger.debug(`Fetching project stats for project ID: ${projectId}`); | ||
|
|
||
| // Mock behavior: simulate network latency. | ||
| await new Promise((resolve) => setTimeout(resolve, API_MOCK_DELAY)); | ||
|
|
||
| // Validate project ID | ||
| const projectExists = mockProjects.some((p) => p.id === projectId); | ||
| if (!projectExists) { | ||
| throw new Error(`Project stats not found for ID: ${projectId}`); | ||
| } | ||
|
|
||
| const stats: ProjectStatsResponse = getMockProjectStats(); | ||
|
|
||
| logger.debug( | ||
| `Project stats fetched successfully for project ID: ${projectId}`, | ||
| stats, | ||
| ); | ||
|
|
||
| return stats; | ||
| }, | ||
| enabled: !!projectId, | ||
| staleTime: 5 * 60 * 1000, | ||
| }); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.