[Customer Portal][FE][Web] Integrate Project Search API and Implement Resilient Error Handling - #91
Conversation
Adjust ProjectHub to match a simplified projects response and improve auth handling. Removed the auto-pagination effect and switched to using projectsResponse.projects directly. Added Asgardeo auth loading into the skeleton/loading state so the UI waits for auth, and wired MockConfig to set the ProjectCard isStatsError flag when mocks are disabled. Also added imports for navigation and providers and left a commented-out single-project auto-navigation snippet for future use.
Add and wire up mocks for useAsgardeo and useMockConfig, and update the ProjectCard mock to surface an isStatsError attribute via data-stats-error. Set sensible default mock returns in beforeEach (auth not loading, mocks enabled, projects returned). Add tests for auth-loading skeletons and for isStatsError behavior when mock config is enabled/disabled. Adjust mocked getProjects data shape in tests (use projects directly) and simplify some existing tests; remove the fetchNextPage test. Overall improves coverage and handles auth/config variations in ProjectHub tests.
Introduce an optional isError prop and import ErrorIndicator. When isError is true, render ErrorIndicator for Open Cases and Active Chats instead of their numeric values, preserving the existing typography/styling otherwise. This surfaces error states in the project card stats UI.
Add an optional isError prop and import ErrorIndicator in ProjectCardBadges. When isError is true, render ErrorIndicator for the status instead of the status Chip; otherwise continue rendering the status Chip with getStatusColor. This surfaces status load errors in the project card UI.
Expose an optional isStatsError prop on ProjectCardProps, destructure it in the component, and forward it as isError to ProjectCardBadges and ProjectCardStats so child components can render an error state for stats.
Add a mock for the ErrorIndicator component and a new test in ProjectCardStats.test.tsx that verifies error indicators are shown when isError is true (for "Open Cases" and "Active Chats") and that numeric counts are not rendered. This supplements existing tests for counts and formatted date.
Mock the ErrorIndicator component in ProjectCardBadges tests and add a test that verifies the error indicator is rendered when isError is true, while the status chip is not displayed. Updates the ProjectCardBadges.test.tsx to improve coverage for error rendering behavior.
Update mocked ProjectCardBadges and ProjectCardStats to accept an isError prop and render 'Error'/'No Error' accordingly. Add a new test that renders ProjectCard with isStatsError=true and asserts the badges and stats sub-components receive and display the error state.
Import MockConfigProvider and expand useAsgardeo destructure to include isSignedIn and isLoading (aliased to isAuthLoading). Update user rendering logic to show the loading state while authentication is loading, and fall back to the error user if no user data is available. Also tidy ternary formatting for readability.
Replace useInfiniteQuery with useQuery in useGetProjects. Integrate Asgardeo auth to obtain ID token and add MockConfigProvider support to return paginated mock data (with API_MOCK_DELAY). Update queryKey to include mock flag, simplify pagination to offset/limit, and remove infinite pagination handlers. Implement real backend POST to /projects/search with Authorization and error handling, add logger messages, and gate execution via an enabled flag (mock enabled or user signed in and auth not loading).
Use isSignedIn and isLoading (aliased to isAuthLoading) from useAsgardeo and update the react-query `enabled` flag so the request only runs when mock mode is active or the user is signed in and auth loading has finished. This prevents the user details fetch from firing prematurely while authentication state is still being determined.
Set a default size prop ('small') for ErrorIndicator and compute an iconSize (16/24/32 for small/medium/large). Pass the computed iconSize to TriangleAlert and use the size prop directly on IconButton (removing the redundant fallback). This ensures consistent visual scaling and prevents undefined size values.
Add a mock for useAsgardeo and extend the ProjectSwitcher test mock to include an isError prop (exposed via data-error). Add tests to verify that Header forwards auth loading (isLoading from useAsgardeo) to ProjectSwitcher and that search projects errors set isError on ProjectSwitcher. Also adjust mocked ProjectSwitcher props handling to assert these behaviors.
Mock the ErrorIndicator component and add a unit test for ProjectSwitcher to verify the error state. The test ensures that when isError is true the error-indicator is rendered (showing "Error: Projects") and the project select is not displayed.
Update UserProfile unit tests to use mockUserDetails and introduce explicit hook mocks. Added mocks for useAsgardeo, MockConfigProvider, and useGetUserDetails and a beforeEach to clear and set default mock returns. Replaced previous assertions to check firstName/lastName/email/timeZone/id and added new test cases for: authenticated rendering, not-signed-in with mocks disabled (should not render), rendering when mocks are enabled, and handling of a failing user fetch. Removed the old useLogger mock and adjusted imports accordingly.
Import useAsgardeo and include its loading state to avoid showing project UI while auth is initializing. Remove pagination/fetchNextPage logic and adapt projects access to use projectsResponse.projects instead of pages.flatMap; update the useMemo dependency accordingly. Also surface isError to the ProjectSwitcher and combine isLoading with auth loading for accurate loading state.
Import ErrorIndicator and add an isError prop to ProjectSwitcher. When isError is true the component renders a styled error box containing ErrorIndicator for the "Projects" entity (preserving the existing loading branch). This surfaces project load failures in the header switcher UI.
📝 WalkthroughWalkthroughThis PR migrates auth to Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant Browser
participant App
participant AuthGuard
participant Asgardeo as AsgardeoProvider
participant Backend
User->>Browser: Navigate to protected route (/projects)
Browser->>App: Route request
App->>AuthGuard: Evaluate access
AuthGuard->>Asgardeo: useAsgardeo() -> isLoading / isSignedIn
Asgardeo-->>AuthGuard: auth state
alt not signed in
AuthGuard->>AuthGuard: ensure loader hidden
AuthGuard->>Browser: Redirect to /login (preserve return state)
Browser->>App: Render LoginPage
User->>LoginPage: Click "Continue with Real APIs"
LoginPage->>Asgardeo: signIn()
Asgardeo->>Backend: Auth flow -> token
Backend-->>Asgardeo: JWT
Asgardeo->>Browser: store token, redirect back
else signed in
AuthGuard->>App: Render protected routes
App->>Backend: Fetch projects (useGetProjects uses token or mock)
Backend-->>App: Projects data
App->>User: Render dashboard (ProjectHub, ProjectCard, etc.)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~65 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Pull request overview
This PR migrates the authentication system from @asgardeo/auth-react to @asgardeo/react, refactors the project fetching logic from infinite scrolling to standard queries, and introduces comprehensive error handling with visual indicators across the application. It also adds a new login page with mock API support and implements auth guards to protect routes.
Changes:
- Migrated from
@asgardeo/auth-reactto@asgardeo/reactpackage and implementedAuthGuardfor route protection - Refactored
useGetProjectsfrom infinite query to standard query pattern with auth-based enabled conditions - Added
ErrorIndicatorcomponents throughout the UI to display API fetch failures gracefully - Introduced login page with mock/real API selection and
MockConfigProviderfor state management
Reviewed changes
Copilot reviewed 39 out of 43 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
package.json |
Updated Asgardeo package dependency from @asgardeo/auth-react to @asgardeo/react |
AppWithConfig.tsx |
Migrated to AsgardeoProvider and added MockConfigProvider wrapper |
App.tsx |
Added AuthGuard wrapper around protected routes and new /login public route |
AuthGuard.tsx |
New authentication guard component that redirects unauthenticated users |
authConfig.ts |
Refactored config validation with improved error messaging |
useGetProjects.ts |
Migrated from infinite query to standard query with mock support and auth guards |
useGetUserDetails.ts |
New hook for fetching user details with mock support |
ProjectHub.tsx |
Added auth loading checks and stats error handling (single-project redirect commented out) |
Header.tsx |
Removed infinite scroll logic, added auth loading state and error propagation |
ProjectSwitcher.tsx |
Added error state rendering with ErrorIndicator |
UserProfile.tsx |
Implemented dynamic user rendering with loading/error states |
ProjectCard.tsx, ProjectCardStats.tsx, ProjectCardBadges.tsx |
Added isStatsError prop with error indicator support |
ErrorIndicator.tsx |
New reusable component for displaying fetch errors with tooltips |
LoginPage.tsx, LoginBox.tsx, LoginSlogan.tsx, etc. |
New login page components with mock API selection |
MockConfigProvider.tsx |
New context provider for managing mock mode state with localStorage persistence |
AppLayout.tsx |
Added optional children prop support |
responses.ts |
Split UserProfile into separate UserDetails interface |
| Various test files | Updated tests to mock new auth hooks and verify error handling |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/customer-portal/webapp/src/components/common/header/__tests__/Header.test.tsx (1)
289-315:⚠️ Potential issue | 🟡 MinorRemove stale tests for infinite query behavior.
These tests reference
fetchNextPageandhasNextPage, which areuseInfiniteQueryreturn values. According to the PR changes,useGetProjectswas converted to useuseQuery, and looking atHeader.tsx(lines 51-55 in the external context), the component only destructuresdata,isLoading, andisError— it no longer usesfetchNextPageorhasNextPage.These tests pass only because the mock provides these values, but they're testing phantom behavior that doesn't exist in the component.
🧹 Proposed fix: Remove stale tests
- it("should call fetchNextPage if hasNextPage is true", () => { - mockUseSearchProjects.mockReturnValue({ - data: { pages: [{ projects: mockProjects }] }, - fetchNextPage: mockFetchNextPage, - hasNextPage: true, - isFetchingNextPage: false, - isError: false, - }); - - render(<Header onToggleSidebar={mockOnToggleSidebar} />); - - expect(mockFetchNextPage).toHaveBeenCalled(); - }); - - it("should NOT call fetchNextPage if isError is true", () => { - mockUseSearchProjects.mockReturnValue({ - data: { pages: [{ projects: mockProjects }] }, - fetchNextPage: mockFetchNextPage, - hasNextPage: true, - isFetchingNextPage: false, - isError: true, - }); - - render(<Header onToggleSidebar={mockOnToggleSidebar} />); - - expect(mockFetchNextPage).not.toHaveBeenCalled(); - });Also consider updating the mock return structure at lines 108-117 and 187-195 to match the new
useQueryreturn shape (removingfetchNextPage,hasNextPage,isFetchingNextPage, andpages).
🤖 Fix all issues with AI agents
In `@apps/customer-portal/webapp/package.json`:
- Line 13: The dependency entry for "@asgardeo/react" in package.json uses a
non-existent version "^0.10.0"; update that dependency to a valid published
version (for example change to "^0.6.30") or verify the package name if a
different package was intended, then run npm install to confirm the fix; locate
the dependency in package.json (the "@asgardeo/react" entry) and replace the
version string accordingly.
In `@apps/customer-portal/webapp/src/api/useGetProjects.ts`:
- Around line 88-100: The POST to `${baseUrl}/projects/search` currently sends
an empty body (const body = {}) causing pagination/search to be ignored; update
the request payload in useGetProjects.ts to include the searchData object (or
explicitly include its pagination fields like limit and offset and any filters)
so the real API receives the same parameters used by the mock path; ensure the
fetch call's body is JSON.stringify(searchData) (or a merged object if
additional metadata is required) and keep Authorization and header usage as-is.
In `@apps/customer-portal/webapp/src/api/useGetUserDetails.ts`:
- Around line 34-81: The query key in useGetUserDetails (queryKey:
["userDetails", isMockEnabled]) is missing a per-user identifier, causing cached
data from one user to be served to another; update useGetUserDetails to include
a stable user identifier in the queryKey (e.g., decode the id token returned by
getIdToken() to extract the subject/user id or call a helper that returns
currentUserId and use queryKey: ["userDetails", currentUserId, isMockEnabled])
and adjust the enabled logic to depend on that id (enabled: isMockEnabled ||
(isSignedIn && !isAuthLoading && !!currentUserId)); alternatively, ensure
signOut() calls queryClient.invalidateQueries(["userDetails"]) (or a broader
user-scoped namespace) to clear user-specific caches—apply the same pattern to
all other user-scoped queries.
In `@apps/customer-portal/webapp/src/AuthGuard.tsx`:
- Around line 42-46: The AuthGuard currently falls through to return <Outlet />
while isLoading is true, allowing protected content to flash; update the
AuthGuard component to explicitly handle the loading state by returning null or
a placeholder/loading component when isLoading is true (before checking
isSignedIn), so only when isLoading is false do you evaluate isSignedIn and
either Navigate to "/login" or render <Outlet />; refer to the isLoading and
isSignedIn checks in AuthGuard and the usage of Navigate and Outlet to locate
where to add the early return for the loading state.
In
`@apps/customer-portal/webapp/src/components/common/header/__tests__/UserProfile.test.tsx`:
- Around line 68-76: The test currently asserts for fields not rendered by
UserProfile; update the assertions in UserProfile.test.tsx to check that
UserMenu receives the combined name and email only: replace separate
firstName/lastName expects with a single expect for the combined name string
built from mockUserDetails (e.g., `${mockUserDetails.firstName}
${mockUserDetails.lastName}`.trim()), keep the expect for mockUserDetails.email,
and remove assertions for timeZone and id since UserProfile only passes name,
email, and avatar to UserMenu.
In `@apps/customer-portal/webapp/src/components/common/header/UserProfile.tsx`:
- Around line 68-71: The errorUser object is missing the email property which
makes it inconsistent with loadingUser and can break UserMenu; update the
errorUser definition (the constant named errorUser) to include an email field
matching the other placeholders (e.g., a placeholder/error indicator or empty
string) so it has name, email, and avatar properties like loadingUser, ensuring
UserMenu receives a consistent shape.
In `@apps/customer-portal/webapp/src/config/authConfig.ts`:
- Line 57: authConfig is being initialized at module load via the exported
constant authConfig = getAuthConfig(), which causes the app to crash before
MockConfigProvider can be set up when auth env vars are missing; change this to
lazy initialization by removing the eager export and instead call
getAuthConfig() at runtime where needed (e.g., inside AppWithConfig.tsx or a new
wrapper component) so the call happens after MockConfigProvider is mounted or
after you detect mock mode; update usages that import authConfig to call
getAuthConfig() (or receive the result via props/context) and ensure
getAuthConfig() errors are handled locally so missing env vars don't throw
during module evaluation.
In `@apps/customer-portal/webapp/src/layouts/AppLayout.tsx`:
- Around line 32-36: AppLayoutProps references React.ReactNode but React isn't
imported, causing a TS compile error; update the file to import the type
directly (e.g., add an import type for ReactNode from 'react') and then use
ReactNode in the interface (or keep React.ReactNode after importing React) so
the AppLayoutProps and the AppLayout component compile correctly; target the
interface AppLayoutProps and the AppLayout function signature to ensure the type
is resolved.
In `@apps/customer-portal/webapp/src/pages/LoginPage.tsx`:
- Around line 40-43: The inline style object in LoginPage uses invalid flex
values alignItems: "top" and justifyContent: "left"; update the style where
these properties are set (the style block applied to the LoginPage container) to
use valid flex values, e.g., replace both with "flex-start" (alignItems:
"flex-start", justifyContent: "flex-start") so the flex layout honors the
intended top-left alignment.
In `@apps/customer-portal/webapp/src/pages/ProjectHub.tsx`:
- Line 159: The prop isStatsError is currently set using inverted mock-mode
logic (isStatsError={!isMockEnabled}), causing production to always show error
states; change the ProjectHub usage to pass a real stats error flag instead:
either wire up the actual stats API error state (e.g., a useState/useSelector
like statsError and pass isStatsError={statsError}) or, if the stats endpoint
isn't implemented yet, set isStatsError={false} until you add real error
handling; update any related data fetching hook (e.g., the fetch or useEffect
that loads stats) to set the chosen statsError variable when the API fails so
the prop reflects real failure.
🧹 Nitpick comments (5)
apps/customer-portal/webapp/src/layouts/AppLayout.tsx (1)
104-108: Prefer nullish coalescing forchildrenfallback.Using
children || ...will treat valid falsy children (0,"",false) as absent.??only falls back whenchildrenisnull/undefined.🔧 Suggested tweak
- {children || ( + {children ?? ( <Outlet context={{ sidebarCollapsed: shellState.sidebarCollapsed }} /> )}apps/customer-portal/webapp/src/components/login-page/ParticleBackground.tsx (1)
19-108: Respectprefers-reduced-motionto avoid forced animation.
Consider skipping the animation when users request reduced motion.♿ Suggested change (respect reduced motion)
useEffect(() => { + const prefersReducedMotion = window.matchMedia( + "(prefers-reduced-motion: reduce)", + ).matches; + if (prefersReducedMotion) return; + const canvas = canvasRef.current; if (!canvas) return;apps/customer-portal/webapp/src/pages/ProjectHub.tsx (1)
35-35: Remove unused import and commented code.
navigateis imported but only referenced in commented-out code (lines 49-53). Either implement the single-project redirect feature or remove both the import and the commented code.Proposed fix
-import { useNavigate } from "react-router"; ... - const navigate = useNavigate(); ... - // useEffect(() => { - // if (!isLoading && !isError && projects.length === 1) { - // navigate(`/${projects[0].id}/dashboard`); - // } - // }, [projects, isLoading, isError, navigate]);Also applies to: 49-53
apps/customer-portal/webapp/src/components/common/header/UserProfile.tsx (1)
35-37: Remove unused destructured variables.
isSignedInandisMockEnabledare destructured but never used in this component.Proposed fix
- const { signOut, isSignedIn, isLoading: isAuthLoading } = useAsgardeo(); - const { isMockEnabled } = useMockConfig(); + const { signOut, isLoading: isAuthLoading } = useAsgardeo(); + useMockConfig(); // Ensure provider context is availableIf
useMockConfig()isn't needed at all, remove line 36 entirely along with the import on line 20.apps/customer-portal/webapp/src/components/common/header/__tests__/Header.test.tsx (1)
108-122: Mock return structure doesn't match the newuseQueryreturn shape.The mock returns
data: { pages: [{ projects: mockProjects }] }, which is theuseInfiniteQuerystructure. Since the hook was converted touseQuery, it now returnsdata: { projects, offset, limit, totalRecords }directly (matchingSearchProjectsResponse).The tests may still pass if the component gracefully handles missing properties, but the mock should accurately reflect the actual data structure for reliable testing.
♻️ Proposed fix to align mock with useQuery return shape
const mockUseSearchProjects = vi.fn(() => ({ data: { - pages: [{ projects: mockProjects }], + projects: mockProjects, + offset: 0, + limit: 100, + totalRecords: mockProjects.length, }, - fetchNextPage: mockFetchNextPage, - hasNextPage: false, - isFetchingNextPage: false, + isLoading: false, isError: false, })) as any;Apply the same change to the
beforeEachblock at lines 187-195.
Remove the unused useNavigate import and its instantiation. Comment out the useEffect that automatically navigates to a single project's dashboard (with a TODO to re-enable in production) to prevent automatic redirection during development/testing.
Replace non-standard alignment values with proper flexbox values and normalize slogan text styling. LoginPage: change alignItems/justifyContent from "top"/"left" to "flex-start" to use valid CSS flexbox values. LoginSlogan: remove explicit fontWeight and reduce fontSize from 1.1rem to 1rem for consistent visual spacing.
Add an explicit ReactNode type import from react and update AppLayoutProps to use ReactNode instead of React.ReactNode in AppLayout.tsx. This is a type-only cleanup with no runtime behavior changes.
Enhance UserProfile unit tests by adding explicit mocks for UserMenu, Skeleton, icons, ErrorIndicator, and useLogger. Replace fragile text-based assertions with data-testid checks for combined user name and email. Add a loading test to assert skeleton rendering and an error test to assert the ErrorIndicator is shown when user fetch fails. Overall improves reliability and coverage of UserProfile behavior under loading/error/authenticated states.
Remove the unused useMockConfig import and its isMockEnabled reference, and stop destructuring isSignedIn from useAsgardeo. Also remove a couple of redundant comment lines to clean up unused code; no functional behavior changes are expected.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@apps/customer-portal/webapp/src/pages/ProjectHub.tsx`:
- Around line 47-52: The commented auto-navigation useEffect refers to navigate
but there is no useNavigate import or navigate variable in ProjectHub (the
symbols involved are useEffect, useNavigate, and navigate, and the state tokens
projects, isLoading, isError), so if uncommented it will throw a ReferenceError;
fix by either adding a commented import line for useNavigate from
react-router-dom immediately above the commented block or updating the TODO to
explicitly state that useNavigate must be imported and navigate must be
initialized (e.g., const navigate = useNavigate()) before uncommenting the
block; ensure the instructions mention the exact symbols useNavigate and
navigate so future maintainers know what to add.
🧹 Nitpick comments (2)
apps/customer-portal/webapp/src/components/login-page/LoginSlogan.tsx (1)
21-49: Minor styling inconsistency: inlinestylevssxprop.Line 40 uses an inline
styleprop while the rest of the component consistently uses thesxprop. For consistency, consider usingBoxfrom@wso2/oxygen-uiwithsxinstead.♻️ Suggested refactor for consistency
-import { Stack, Typography } from "@wso2/oxygen-ui"; +import { Box, Stack, Typography } from "@wso2/oxygen-ui";- <div style={{ display: "flex", alignItems: "center" }}> - {item.icon} - </div> + <Box sx={{ display: "flex", alignItems: "center" }}> + {item.icon} + </Box>apps/customer-portal/webapp/src/components/common/header/__tests__/UserProfile.test.tsx (1)
112-118: Consider adding a test foruseGetUserDetailsloading state.The current test only covers auth loading (
isLoading: truefromuseAsgardeo). If the component also shows skeletons whenuseGetUserDetails.isLoadingis true (while auth is complete), consider adding a separate test case for that scenario:it("should render skeletons when user details are loading", () => { mockUseAsgardeo.mockReturnValue({ isSignedIn: true, isLoading: false }); mockUseGetUserDetails.mockReturnValue({ data: null, isLoading: true, isError: false, }); render(<UserProfile />); expect(screen.getAllByTestId("skeleton")).toHaveLength(2); });This ensures both loading paths are verified independently.
Rashmika998
left a comment
There was a problem hiding this comment.
LGTM
directory refactor and path changes will be tracked under #89
c89225d
into
wso2-open-operations:customer-portal-milestone-1
Purpose
This PR focuses on the robust integration of the
useGetProjectsAPI across the primary application interfaces (ProjectHub,Header, andProjectSwitcher). It transitions the data fetching model from infinite scrolling to a standardized query structure and introduces a comprehensive error-handling strategy with granular visual indicators.Goals
ErrorIndicatorcomponents within cards and dropdowns to gracefully handle partial API failures.Approach
useGetProjectsanduseGetUserDetailswith strictenabledconditions (isSignedIn && !isAuthLoading), preventing "SDK not initialized" errors.isErrorandisStatsErrorprops. If the API fails,ProjectCardStatsandProjectCardBadgesnow render a "Failed to load" tooltip and icon instead of empty or broken values.ProjectSwitchernow detects fetch errors and replaces the dropdown menu with a small-scaleErrorIndicator.ProjectHubto detect if the result set contains exactly one project; if so, the user is automatically navigated to that project's dashboard.ErrorIndicatorto support a 16px icon size for seamless integration into dense UI areas like stats rows.User stories
Walkthrough
This PR integrates the project search API with the core UI. It refactors the fetching logic to align with standard query responses, adds auth-state guards to API hooks, and implements visual error indicators across the Header, Project Switcher, and Project Cards. It also includes an automatic redirect for single-project users and updated unit tests for all scenarios.
Changes
useGetProjects.ts,useGetUserDetails.tsenabledconditions; fixed request body construction.ProjectHub.tsxHeader.tsx,ProjectSwitcher.tsxErrorIndicatorlogic.ProjectCard.tsx,Stats.tsx,Badges.tsxisStatsErrorUI to show "Failed to load" tooltips during failures.ErrorIndicator.tsxProjectHub.test.tsx,Header.test.tsx, etc.Sequence Diagram
sequenceDiagram participant SDK as Auth SDK participant Hub as ProjectHub participant API as useGetProjects participant Router as React Router Hub->>SDK: Check auth state Note over Hub, API: enabled: isSignedIn && !isAuthLoading SDK-->>Hub: Auth Ready (Signed In) Hub->>API: Fetch Projects API-->>Hub: Return Results (1 Project Found) alt Single Project Case Hub->>Router: Navigate to /:projectId/dashboard else Multiple Projects Case Hub-->>User: Render Project List end alt API Error Case API-->>Hub: Fetch Error Hub->>Hub: Set isStatsError(true) Hub-->>User: Show "Failed to load" on Card Stats endAutomation tests
isStatsErrorcorrectly renders theErrorIndicatorin both badges and stats sections.UserProfileandHeadertests to support new authentication mock structures.Summary by CodeRabbit
New Features
Improvements