Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 87 additions & 70 deletions apps/customer-portal/webapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import { SuccessBannerProvider } from "@context/success-banner/SuccessBannerCont
import { LoaderProvider } from "@context/linear-loader/LoaderContext";
import LoginPage from "@pages/LoginPage";
import AuthGuard from "./AuthGuard";
import SecurityPage from "./pages/SecurityPage";
import VulnerabilityDetailsPage from "./pages/VulnerabilityDetailsPage";
Comment thread
dileepapeiris marked this conversation as resolved.

export default function App(): JSX.Element {
return (
Expand All @@ -42,81 +44,96 @@ export default function App(): JSX.Element {
<LoaderProvider>
<ErrorBannerProvider>
<SuccessBannerProvider>
<Routes>
{/* Public Route */}
<Route path="/login" element={<LoginPage />} />
<Routes>
{/* Public Route */}
<Route path="/login" element={<LoginPage />} />

{/* Protected Routes */}
<Route element={<AuthGuard />}>
{/* AppLayout component */}
<Route element={<AppLayout />}>
{/* ProjectHub Page */}
<Route path="/" element={<ProjectHub />} />
{/* Protected Routes */}
<Route element={<AuthGuard />}>
{/* AppLayout component */}
<Route element={<AppLayout />}>
{/* ProjectHub Page */}
<Route path="/" element={<ProjectHub />} />

{/* Project Specific Routes */}
<Route path="/:projectId">
{/* Dashboard */}
<Route index element={<Navigate to="dashboard" replace />} />
<Route path="dashboard" element={<DashboardPage />} />
{/* Project Details */}
<Route path="project-details" element={<ProjectDetails />} />
{/* Support */}
<Route path="support">
<Route index element={<SupportPage />} />
<Route path="cases">
<Route index element={<AllCasesPage />} />
<Route path=":caseId" element={<CaseDetailsPage />} />
{/* Project Specific Routes */}
<Route path="/:projectId">
{/* Dashboard */}
<Route
index
element={<Navigate to="dashboard" replace />}
/>
<Route path="dashboard" element={<DashboardPage />} />
{/* Project Details */}
<Route
path="project-details"
element={<ProjectDetails />}
/>
{/* Support */}
<Route path="support">
<Route index element={<SupportPage />} />
<Route path="cases">
<Route index element={<AllCasesPage />} />
<Route path=":caseId" element={<CaseDetailsPage />} />
</Route>
<Route path="chat">
<Route index element={<NoveraChatPage />} />
<Route
path="create-case"
element={<CreateCasePage />}
/>
</Route>
</Route>
{/* Updates */}
<Route path="updates">
<Route index element={<UpdatesPage />} />
<Route
path="pending"
element={<PendingUpdatesPage />}
/>
</Route>
{/* SecurityCenter */}
<Route path="security-center">
<Route index element={<SecurityPage />} />
<Route
path=":vulnerabilityId"
element={<VulnerabilityDetailsPage />}
/>
</Route>
{/* Engagements */}
<Route
path="engagements"
element={<ProjectPage title="Engagements" />}
/>
{/* LegalContracts */}
<Route
path="legal-contracts"
element={<ProjectPage title="Legal Contracts" />}
/>
{/* Community */}
<Route
path="community"
element={<ProjectPage title="Community" />}
/>
{/* Announcements */}
<Route
path="announcements"
element={<ProjectPage title="Announcements" />}
/>
{/* Settings */}
<Route
path="settings"
element={<ProjectPage title="Settings" />}
/>
</Route>
</Route>
<Route path="chat">
<Route index element={<NoveraChatPage />} />
<Route path="create-case" element={<CreateCasePage />} />
</Route>
</Route>
{/* Updates */}
<Route path="updates">
<Route index element={<UpdatesPage />} />
<Route path="pending" element={<PendingUpdatesPage />} />
</Route>
{/* SecurityCenter */}
<Route
path="security-center"
element={<ProjectPage title="Security Center" />}
/>
{/* Engagements */}
<Route
path="engagements"
element={<ProjectPage title="Engagements" />}
/>
{/* LegalContracts */}
<Route
path="legal-contracts"
element={<ProjectPage title="Legal Contracts" />}
/>
{/* Community */}
<Route
path="community"
element={<ProjectPage title="Community" />}
/>
{/* Announcements */}
<Route
path="announcements"
element={<ProjectPage title="Announcements" />}
/>
{/* Settings */}
<Route
path="settings"
element={<ProjectPage title="Settings" />}
/>
</Route>
</Route>
</Route>

{/* Fallback */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</SuccessBannerProvider>
</ErrorBannerProvider>
</LoaderProvider>
{/* Fallback */}
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</SuccessBannerProvider>
</ErrorBannerProvider>
</LoaderProvider>
</AuthApiProvider>
</BrowserRouter>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// 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, Card, Typography } from "@wso2/oxygen-ui";
import { type JSX, type ReactNode } from "react";

export interface GenericSubCountCardProps {
label: string;
value: string | number | ReactNode;
icon: ReactNode;
color?: string;
footerContent?: ReactNode;
}

/**
* Generic stat card with label, value, icon, and optional footer.
* @returns {JSX.Element}
*/
const GenericSubCountCard = ({
label,
value,
icon,
color = "primary.main",
footerContent,
}: GenericSubCountCardProps): JSX.Element => {
return (
<Card
sx={{
display: "flex",
flexDirection: "column",
gap: 2,
p: 2.5,
height: "100%",
}}
>
<Box sx={{ display: "flex", alignItems: "flex-start", gap: 2 }}>
<Box sx={{ color }}>{icon}</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="h4">{value}</Typography>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Rendering arbitrary ReactNode inside <h4> risks invalid HTML

Typography variant="h4" renders to <h4>. If a caller passes a ReactNode containing block-level elements (div, p, section, etc.) as value, the result is invalid HTML (block element nested inside a heading). Since the type explicitly allows ReactNode, this is easy to trigger accidentally.

Consider restricting value back to string | number if complex nodes are not intended, or changing the container to a Box (renders as div) when value is not a primitive.

🛡️ Safer container for arbitrary ReactNode values
-  <Typography variant="h4">{value}</Typography>
+  {typeof value === "string" || typeof value === "number" ? (
+    <Typography variant="h4">{value}</Typography>
+  ) : (
+    <Box>{value}</Box>
+  )}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Typography variant="h4">{value}</Typography>
{typeof value === "string" || typeof value === "number" ? (
<Typography variant="h4">{value}</Typography>
) : (
<Box>{value}</Box>
)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/customer-portal/webapp/src/components/common/GenericSubCountCard.tsx` at
line 52, The component GenericSubCountCard currently renders the prop value
directly inside <Typography variant="h4"> which outputs an <h4>, risking invalid
HTML if callers pass a ReactNode containing block elements; update
GenericSubCountCard to restrict the value prop type to string | number (instead
of ReactNode) OR render conditionally: if typeof value is 'string' or 'number'
keep <Typography variant="h4">{value}</Typography>, otherwise wrap the node in a
neutral container (e.g., Box/div) instead of the h4; adjust the value prop type
and any callers accordingly and ensure propTypes/TS types for value and the
rendering logic in GenericSubCountCard reflect this change.

<Typography variant="body2" color="text.secondary">
{label}
</Typography>
</Box>
</Box>
{footerContent && (
<Box sx={{ borderTop: 1, borderColor: "divider", pt: 2 }}>
{footerContent}
</Box>
)}
</Card>
);
};

export default GenericSubCountCard;
Comment thread
dileepapeiris marked this conversation as resolved.
Loading
Loading