Skip to content

[Customer Portal Micro App] Initial Frontend Architecture and Core UI Implementation#9

Open
lithika-damnod wants to merge 68 commits intowso2-open-operations:customer-portal-milestone-1from
lithika-damnod:frontend/customer-portal-mobile
Open

[Customer Portal Micro App] Initial Frontend Architecture and Core UI Implementation#9
lithika-damnod wants to merge 68 commits intowso2-open-operations:customer-portal-milestone-1from
lithika-damnod:frontend/customer-portal-mobile

Conversation

@lithika-damnod
Copy link

@lithika-damnod lithika-damnod commented Jan 5, 2026

Summary

This PR introduces the initial frontend architecture and core UI implementation for the Customer Portal Micro Application, establishing the main application structure, reusable components, and a fully functional mobile interface.

Summary by CodeRabbit

  • New Features

    • Customer Portal microapp: project selection, app chrome, routing, dashboards, charts & widgets, detailed views (cases/changes/services) and timelines
    • Support & chat: interactive chat, message bubbles, feedback, KB cards, conversation summaries, create-case flow, sticky comment/chat input
    • User & project management: invite/edit users, role selection, user list, project cards/selector, notifications, profile/settings
    • Native bridge and backend integration surface for token, storage, QR, and logging
  • Documentation

    • Environment/config README and example env entry
  • Chores

    • Project tooling: package manifest, lint/format, TypeScript and Vite configs, editor/git ignores

…loading due to incomplete config support for layout
@coderabbitai
Copy link

coderabbitai bot commented Jan 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new customer-portal micro-frontend (Vite + React + TypeScript): project/tooling files, runtime config, contexts/providers, theme, many UI primitives and feature components, pages, services (api/auth/projects), a native bridge facade, extensive mocks, and documentation.

Changes

Cohort / File(s) Summary
Project config & tooling
apps/customer-portal/microapp/.env.example, apps/customer-portal/microapp/.gitignore, apps/customer-portal/microapp/.prettierrc, apps/customer-portal/microapp/package.json, apps/customer-portal/microapp/eslint.config.js, apps/customer-portal/microapp/vite.config.ts, apps/customer-portal/microapp/tsconfig*.json
Add env example, gitignore, Prettier/ESLint configs, package manifest, Vite config, and TypeScript project/paths configs.
App bootstrap & entry
apps/customer-portal/microapp/index.html, .../src/main.tsx, .../src/App.tsx, .../src/index.css
Add HTML entry, React bootstrap wiring (Auth/Theme/Query providers), app routes (HashRouter), and global CSS/font.
Runtime config & environment
.../src/config/config.ts, .../src/config/endpoints.ts, .../src/vite-env.d.ts, apps/customer-portal/microapp/.env.example
Introduce window.config typing/exports, enforce VITE_BACKEND_URL at runtime, and expose BACKEND_URL and endpoints.
Theme & ui constants
.../src/theme/*, .../src/config/constants.ts, .../src/config/constants.ts, .../src/utils/constants.ts
Add theme extension, typography, project metric/type/status metadata, and shared string/localStorage constants.
Contexts & providers
.../src/context/*, .../src/context/layout/*, .../src/context/project/*, .../src/context/AppProvider.tsx
Add Layout and Project contexts, providers, hooks (useLayout/useProject), layout config (MAIN_LAYOUT_CONFIG, APP_BAR_CONFIG) and AppProvider composition.
Core layout & navigation
.../src/components/layout/*, .../src/components/core/*
Add MainLayout, RequireProject guard, AppBar, TabBar, and layout integration.
UI primitives & shared
.../src/components/ui/*, .../src/components/shared/*, .../src/components/ui/index.ts, .../src/components/shared/index.ts
Add NotificationBadge, WidgetBox, Timeline wrapper, SectionCard, TabPanel and barrels.
Microapp native bridge & types
.../src/components/microapp-bridge/*, .../src/components/microapp-bridge/types.ts
Add native bridge facade and typed topics: token, QR, local storage, alerts, TOTP, and native logging helpers.
Services
.../src/services/apiClient.ts, .../src/services/auth.ts, .../src/services/projects.ts
Add Axios apiClient with token refresh/queue/retry, auth token handlers/refresh/user init, and projects fetcher mapping backend to UI model.
Store & utilities
.../src/store/*, .../src/utils/*, .../src/utils/logger.ts
Add Zustand user store, Logger wrapper, stringAvatar/capitalize utilities, and constants.
Feature components (barrels)
apps/customer-portal/microapp/src/components/features/...
Large set of new feature components and barrels: chat, dashboard widgets, support (ItemCard(s), filters), detail components (TimelineEntry, StickyCommentBar, etc.), projects, users, settings, create-flow fields/summary, notifications.
Pages & route wiring
apps/customer-portal/microapp/src/pages/*.tsx
Add many pages wired to routes and layout: Home, Support, Users, Profile, Chat, ChatDetail, CaseDetail, ChangeDetail, ServiceDetail, CreateCase, EditUser, Notifications, SelectProject, AllItemsPage and helper slots.
Mocks
apps/customer-portal/microapp/src/mocks/data/*
Add extensive mock datasets for home, projects, users, notifications, support items, cases, services, and changes.
Barrels & exports
multiple apps/customer-portal/microapp/src/**/index.ts
Add many index/barrel files re-exporting UI, shared, core, and feature components.
Docs
apps/customer-portal/microapp/README.md
Add README with runtime config template (public/config.js) and setup instructions.

Sequence Diagram(s)

sequenceDiagram
  participant UI as Client (React)
  participant API as Backend (API)
  participant Auth as Auth service (auth.ts / localStorage)
  participant Native as NativeBridge (ReactNativeWebView)

  UI->>API: Send request via apiClient
  API-->>UI: 401 Unauthorized
  UI->>Auth: Attempt refresh (refreshToken)
  Auth->>Native: Request token (postMessage TOPIC.token)
  Native-->>Auth: Resolve token (token payload)
  Auth-->>UI: Store tokens (access/id)
  UI->>API: Retry original request with new token
  API-->>UI: 200 OK (response)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

Type/New Feature, App/Customer Portal, Area/Frontend, Platform/Web

Suggested reviewers

  • Rashmika998

Poem

🐰 I hopped in code with nimble feet and glee,
New pages, widgets, bridges — stitched carefully,
Mocks in my pouch, routes set straight and true,
A microapp garden bloomed from just a few,
Hop, build, and run — a carrot waits for you!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal and only provides a summary section. The template requires multiple sections (Purpose, Goals, Approach, User stories, Release note, Documentation, etc.) that are entirely missing. Complete the PR description by adding all required template sections: Purpose/Goals, Approach with UI/documentation links, User stories, Release note, Documentation impact, Training, Certification, Marketing, test coverage details, Security checks, Samples, Related PRs, and Test environment information.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: establishing the initial frontend architecture and core UI implementation for a Customer Portal Micro App.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@lithika-damnod
Copy link
Author

📌   Snapshots / UI References

@lithika-damnod lithika-damnod marked this pull request as draft January 5, 2026 17:40
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 15

🤖 Fix all issues with AI agents
In `@apps/customer-portal/microapp/package.json`:
- Around line 32-33: In package.json, move the Vite build plugins
"vite-plugin-svgr" and "vite-tsconfig-paths" out of the dependencies object and
add them under devDependencies instead; update the dependencies and
devDependencies entries accordingly, then run the package manager to update the
lockfile (npm install / yarn install / pnpm install) so the lockfile reflects
the change. This ensures the runtime "dependencies" object no longer contains
"vite-plugin-svgr" and "vite-tsconfig-paths" and that they exist only under
"devDependencies".

In
`@apps/customer-portal/microapp/src/components/features/chat/MessageBubble.tsx`:
- Around line 35-62: The switch over block.type inside blocks.map (in
MessageBubble.tsx) lacks a default branch, causing the map callback to return
undefined for unknown block types; add a default case to the switch (or an
exhaustive never assertion) that returns null (or logs/throws) to satisfy the
linter and ensure exhaustiveness for block rendering in the blocks.map callback;
update the switch handling in the MessageBubble component (the switch on
block.type and the map callback) accordingly.

In
`@apps/customer-portal/microapp/src/components/features/projects/ProjectCard.tsx`:
- Around line 34-46: ProjectCardProps declares numberOfOpenCases but ProjectCard
does not use it; either remove numberOfOpenCases from the ProjectCardProps
interface (and any callers passing it) to eliminate the unused prop, or if it
should be shown, add numberOfOpenCases to the ProjectCard parameter
destructuring and render it in the component UI (e.g., near metrics or status)
so the prop is consumed; locate the symbols ProjectCardProps, numberOfOpenCases,
and ProjectCard to apply the chosen fix.
- Around line 69-80: The Grid elements created in ProjectCard.tsx within the
Object.keys(metrics).map iterator are missing a React key prop; update the map
callback that renders <Grid> (and contains <MetricItem>) to pass a stable key,
e.g., key={key} (using the metric key string from the map) so React can properly
reconcile the list; the change should be applied where
Object.keys(metrics).map((key) => { ... return (<Grid ...>...); }) produces
Grid, and keep existing usage of PROJECT_METRIC_META and ProjectMetricKey
unchanged.

In `@apps/customer-portal/microapp/src/pages/CreateCasePage.tsx`:
- Around line 67-82: The form in CreateCasePage is using hardcoded test data and
a debug alert and lacks validation; update the formik setup by clearing test
strings in initialValues (set title and description to empty ""), replace the
alert in onSubmit with the real case-creation API call (invoke your
createCase/createCaseApi function and handle success/error, then navigate), and
add a simple Formik validationSchema or validate function (e.g., require
non-empty title and other required fields) to prevent submitting
empty/nonsensical values; locate the useFormik call and adjust initialValues,
onSubmit, and add validationSchema/validate accordingly.

In `@apps/customer-portal/microapp/src/pages/EditUserPage.tsx`:
- Around line 94-100: The SendButton component currently navigates to "/users"
via the Link prop and never triggers any form submission or API call (SendButton
in EditUserPage), so form data is discarded; change SendButton to either call
the form submit handler or dispatch the invitation/save action instead of using
component={Link} (or make it type="submit" and let the surrounding form handle
submission), and if you intentionally left submission unimplemented add a clear
TODO comment in SendButton referencing the missing submit/invite function (e.g.,
TODO: call submitInvite or dispatch saveUser) so the missing behavior is
tracked.
- Around line 49-51: The Typography currently renders a hardcoded "Last Active:
2 hours ago"; update EditUserPage to use the user's real last-active value
(e.g., user.lastActive or state.lastActive) and conditionally render a formatted
relative time (use a helper like formatDistanceToNow(new Date(...)) + " ago") or
a clear placeholder ("—" or "Last Active: —") when the value is missing; target
the Typography element in EditUserPage and replace the static string with the
conditional rendering/formatting logic so stale information is not shown.
- Around line 110-121: In ExpirationNotice, replace the invalid alignItems="top"
on the Card (which renders a Stack) with a valid flexbox value like
alignItems="flex-start" so the vertical alignment of the icon works correctly;
update the prop on the Card/Stack component usage in ExpirationNotice
accordingly.
- Around line 14-16: location.state currently types role as string but it's used
to initialize useState<RoleName> (in the EditUserPage component), creating a
type-safety gap; update the location state typing so role is typed as RoleName
(e.g., const state = location.state as { email?: string; role?: RoleName; name?:
string }) or validate/coerce state.role into a RoleName before passing it to
setRole/useState<RoleName> to ensure only valid RoleName values are accepted.
Ensure references to state?.role and the useState<RoleName> initializer are
adjusted accordingly (or validated) so TypeScript enforces the RoleName type.

In `@apps/customer-portal/microapp/src/services/apiClient.ts`:
- Around line 96-108: The response interceptor
(apiClient.interceptors.response.use) currently spreads response.data into the
Logger.info payload and computes size via JSON.stringify which risks logging
sensitive content and can be expensive or fail on circular refs; update the
interceptor to stop including response.data contents in the log (remove the
spread), log only metadata (method, url, status, statusText, headers, and a
boolean or capped numeric field like hasData and dataSize), and compute dataSize
safely (wrap JSON.stringify in try/catch or use a safe size check that caps the
value) before calling Logger.info to avoid large or sensitive payloads being
logged.
- Around line 43-49: The current Logger.info call in apiClient.ts logs
config.headers (which can include Authorization tokens); remove headers from the
logged meta or explicitly redact/mask sensitive header values before logging.
Update the Logger.info invocation that builds the request log (the call
referencing Logger.info, config.method, config.baseURL, config.url, and
config.headers) to omit config.headers entirely or replace it with a
sanitizedHeaders object that removes or masks the Authorization header (e.g.,
only log header names or a copy with Authorization replaced by "[REDACTED]").
- Around line 40-79: The request interceptor currently always triggers
refreshToken() via the shared refreshTokenPromise (reset to null in its
finally), causing a refresh on every request; change the logic in
apiClient.interceptors.request.use to first read the existing stored token
(e.g., getAccessToken or whatever storage is used) and use it if present and not
expired, only creating/awaiting refreshTokenPromise when there is no token or it
is expired, and keep the promise alive until a new token is obtained (do not
eagerly clear refreshTokenPromise in finally so concurrent callers share the
same refresh); otherwise rely on the 401 response interceptor to perform a
refresh flow. Ensure you update references to refreshTokenPromise and
refreshToken() accordingly and keep logging around token-added vs token-missing
paths.

In `@apps/customer-portal/microapp/src/services/auth.ts`:
- Around line 73-86: The checkUserGroups function currently calls jwtDecode
without handling malformed/invalid tokens; wrap the jwtDecode call (and any
dependent logic using decoded.groups) in a try/catch block, mirroring the
pattern used in decodeTokenAndStoreUser: on error catch and Logger.error the
exception (including context such as the token presence) and return false so
exceptions don't propagate; ensure you still return false when token is missing
or decoding fails and keep using getIdToken, jwtDecode, and the
requiredGroups/userGroups logic as before.
- Around line 93-117: The function decodeTokenAndStoreUser currently builds a
User object but never writes it to the Zustand store; update
decodeTokenAndStoreUser to call useUserStore.getState().setUser(user) after
constructing the user (before returning) so the parsed user is persisted, and
ensure error paths still call useUserStore.getState().clearUser() as they
already do; reference: decodeTokenAndStoreUser, useUserStore.getState().setUser,
useUserStore.getState().clearUser, getIdToken, jwtDecode.
- Around line 46-48: The callback passed to the native bridge's getToken is
incorrectly treating a single token as both an ID token and an access token: it
calls setIdToken(newIdToken) and setAccessToken(newIdToken). Fix by updating the
bridge and caller so getToken returns distinct tokens (e.g., { idToken,
accessToken }) and then call setIdToken(idToken) and
setAccessToken(accessToken); if you cannot change the bridge immediately, only
call setIdToken(newIdToken) (remove setAccessToken) and add a TODO comment and a
logging/validation path to surface that accessToken is missing. Update the
parameter name (newIdToken -> { idToken, accessToken } or newToken) and adjust
any consumers accordingly.
🧹 Nitpick comments (9)
apps/customer-portal/microapp/src/components/features/dashboard/PieChartWidget.tsx (2)

6-12: Index signature loosens type safety on PieDataItem.

The [key: string]: string | number index signature allows arbitrary properties and suppresses compile-time errors for typos or unexpected keys. If this is required by the chart library's data contract, consider documenting that reason with an inline comment. Otherwise, removing it would give you stricter type checking.


50-51: Consider using item.label as the key instead of array index.

If labels are guaranteed unique within a dataset (which they should be for a meaningful pie chart), item.label is a more stable React key than the array index.

Proposed fix
-        {data.map((item, index) => (
-          <Stack key={index} direction="row" justifyContent="space-between">
+        {data.map((item) => (
+          <Stack key={item.label} direction="row" justifyContent="space-between">
apps/customer-portal/microapp/src/pages/CreateCasePage.tsx (3)

36-36: messages lacks a type annotation — falls back to any.

location.state is unknown (React Router v6), so messages is implicitly any. An explicit type guard or cast to ChatMessage[] (the type ConversationSummary expects) would catch shape mismatches at compile time.

Suggested fix
-  const messages = location.state?.messages || [];
+  const messages: ChatMessage[] = (location.state as { messages?: ChatMessage[] })?.messages ?? [];

(Import ChatMessage from wherever the type is defined.)


38-65: Consider hoisting static option arrays outside the component.

These arrays are constant and don't depend on props or state. Declaring them at module scope avoids re-creating them on every render. While React Compiler can optimize away unnecessary re-renders downstream, keeping constants at module scope is a clearer signal of intent and a standard React convention.


84-148: Form fields do not surface validation errors to the user.

Even once a validationSchema is added, the current JSX never reads formik.errors or formik.touched, so users would get no inline feedback on invalid input. Worth wiring up error / helperText props on the field components when validation is introduced.

apps/customer-portal/microapp/src/pages/EditUserPage.tsx (1)

83-92: Local SectionCard duplicates @components/shared/SectionCard.tsx.

A shared SectionCard already exists (with slightly different spacing/typography). Consider reusing it to avoid divergence.

apps/customer-portal/microapp/src/services/auth.ts (1)

24-30: given_name and family_name are marked as required but may be absent in the JWT.

Standard OIDC claims like given_name and family_name are optional. If the token doesn't include them, jwtDecode won't throw — they'll just be undefined at runtime despite the type claiming they're present. The fallbacks at line 108 (|| "") handle it at the value level, but the type is misleading.

Proposed fix: mark as optional
 interface TokenPayload {
   email?: string;
   name?: string;
   groups?: string[];
-  given_name: string;
-  family_name: string;
+  given_name?: string;
+  family_name?: string;
 }
apps/customer-portal/microapp/src/services/apiClient.ts (2)

155-155: Stray console.error alongside Logger.error.

Line 153 already logs via Logger.error. The console.error on line 155 is redundant and bypasses the structured logging pipeline.

♻️ Proposed fix
       } catch (refreshError) {
         Logger.error("Token refresh failed", refreshError);
         processQueue(refreshError, null);
-        console.error("Token refresh failed:", refreshError);
         return Promise.reject(refreshError);

22-30: The refreshTokenPromise module-level variable is no longer needed if the request interceptor is reworked.

If the request interceptor is updated to check the stored token first (per the earlier comment), this variable can be removed, simplifying the module state.

Comment on lines +67 to +82
const formik = useFormik<CreateCaseFormValues>({
initialValues: {
project: 0,
product: 0,
deployment: 0,
title: "API Gateway timeout issues in production",
description:
"Novera: Hi! I'm Novera, your AI-powered support assistant. How can I help you today? Please describe the issue you're experiencing. Customer: fadfad Novera: Thanks for those details. Based on what you've shared, here are a few things to check:",
type: 0,
severity: 0,
},
onSubmit: (values) => {
alert(JSON.stringify(values, null, 2));
navigate("/support");
},
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded test data and alert() should be removed before merge.

  • Lines 72–74: The initial title and description contain what appears to be debug/test content (e.g., "fadfad", placeholder conversation text). These should be empty strings for production.
  • Line 79: alert(JSON.stringify(values)) is a debug artifact; replace with the actual case-creation API call.
  • No validationSchema (or validate) is provided to Formik, so the form can be submitted with empty or nonsensical values. Consider adding at least basic required-field validation (e.g., non-empty title).
Suggested cleanup
   const formik = useFormik<CreateCaseFormValues>({
     initialValues: {
       project: 0,
       product: 0,
       deployment: 0,
-      title: "API Gateway timeout issues in production",
-      description:
-        "Novera: Hi! I'm Novera, your AI-powered support assistant. How can I help you today? Please describe the issue you're experiencing. Customer: fadfad Novera: Thanks for those details. Based on what you've shared, here are a few things to check:",
+      title: "",
+      description: "",
       type: 0,
       severity: 0,
     },
+    validationSchema: createCaseValidationSchema, // define with Yup or use `validate`
     onSubmit: (values) => {
-      alert(JSON.stringify(values, null, 2));
+      // TODO: call case-creation API
       navigate("/support");
     },
   });
🤖 Prompt for AI Agents
In `@apps/customer-portal/microapp/src/pages/CreateCasePage.tsx` around lines 67 -
82, The form in CreateCasePage is using hardcoded test data and a debug alert
and lacks validation; update the formik setup by clearing test strings in
initialValues (set title and description to empty ""), replace the alert in
onSubmit with the real case-creation API call (invoke your
createCase/createCaseApi function and handle success/error, then navigate), and
add a simple Formik validationSchema or validate function (e.g., require
non-empty title and other required fields) to prevent submitting
empty/nonsensical values; locate the useFormik call and adjust initialValues,
onSubmit, and add validationSchema/validate accordingly.

Comment on lines +43 to +49
Logger.info(`Making ${config.method?.toUpperCase()} request to: ${config.baseURL || ""}${config.url || ""}`, {
url: config.url,
method: config.method,
baseURL: config.baseURL,
fullURL: `${config.baseURL || ""}${config.url || ""}`,
headers: config.headers,
});
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Request headers are logged on every outgoing request — potential sensitive data exposure.

The log at lines 43-49 includes config.headers, which will contain the Authorization bearer token after it's set. Even with a custom logger bridge, logging auth tokens increases the risk of credential leakage in logs.

🛡️ Proposed fix: omit headers from the request log
     Logger.info(`Making ${config.method?.toUpperCase()} request to: ${config.baseURL || ""}${config.url || ""}`, {
       url: config.url,
       method: config.method,
-      baseURL: config.baseURL,
-      fullURL: `${config.baseURL || ""}${config.url || ""}`,
-      headers: config.headers,
     });
📝 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
Logger.info(`Making ${config.method?.toUpperCase()} request to: ${config.baseURL || ""}${config.url || ""}`, {
url: config.url,
method: config.method,
baseURL: config.baseURL,
fullURL: `${config.baseURL || ""}${config.url || ""}`,
headers: config.headers,
});
Logger.info(`Making ${config.method?.toUpperCase()} request to: ${config.baseURL || ""}${config.url || ""}`, {
url: config.url,
method: config.method,
});
🤖 Prompt for AI Agents
In `@apps/customer-portal/microapp/src/services/apiClient.ts` around lines 43 -
49, The current Logger.info call in apiClient.ts logs config.headers (which can
include Authorization tokens); remove headers from the logged meta or explicitly
redact/mask sensitive header values before logging. Update the Logger.info
invocation that builds the request log (the call referencing Logger.info,
config.method, config.baseURL, config.url, and config.headers) to omit
config.headers entirely or replace it with a sanitizedHeaders object that
removes or masks the Authorization header (e.g., only log header names or a copy
with Authorization replaced by "[REDACTED]").

Comment on lines +96 to +108
apiClient.interceptors.response.use(
(response) => {
// Any status code within the range of 2xx causes this function to trigger
Logger.info(`Successful response from ${response.config.method?.toUpperCase()} ${response.config.url}`, {
status: response.status,
statusText: response.statusText,
url: response.config.url,
data: {
...response.data,
dataSize: response.data ? JSON.stringify(response.data).length : 0,
},
});
return response;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Response interceptor logs entire response.data — risk of logging sensitive payload and performance overhead.

Spreading response.data into the log object (lines 103-106) can inadvertently expose sensitive information (PII, secrets) in logs. Additionally, JSON.stringify(response.data) for size calculation is expensive on large payloads and can throw on circular references.

🛡️ Proposed fix: log only metadata, not data content
     Logger.info(`Successful response from ${response.config.method?.toUpperCase()} ${response.config.url}`, {
       status: response.status,
       statusText: response.statusText,
       url: response.config.url,
-      data: {
-        ...response.data,
-        dataSize: response.data ? JSON.stringify(response.data).length : 0,
-      },
     });
📝 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
apiClient.interceptors.response.use(
(response) => {
// Any status code within the range of 2xx causes this function to trigger
Logger.info(`Successful response from ${response.config.method?.toUpperCase()} ${response.config.url}`, {
status: response.status,
statusText: response.statusText,
url: response.config.url,
data: {
...response.data,
dataSize: response.data ? JSON.stringify(response.data).length : 0,
},
});
return response;
apiClient.interceptors.response.use(
(response) => {
// Any status code within the range of 2xx causes this function to trigger
Logger.info(`Successful response from ${response.config.method?.toUpperCase()} ${response.config.url}`, {
status: response.status,
statusText: response.statusText,
url: response.config.url,
});
return response;
🤖 Prompt for AI Agents
In `@apps/customer-portal/microapp/src/services/apiClient.ts` around lines 96 -
108, The response interceptor (apiClient.interceptors.response.use) currently
spreads response.data into the Logger.info payload and computes size via
JSON.stringify which risks logging sensitive content and can be expensive or
fail on circular refs; update the interceptor to stop including response.data
contents in the log (remove the spread), log only metadata (method, url, status,
statusText, headers, and a boolean or capped numeric field like hasData and
dataSize), and compute dataSize safely (wrap JSON.stringify in try/catch or use
a safe size check that caps the value) before calling Logger.info to avoid large
or sensitive payloads being logged.

Comment on lines +46 to +48
if (newIdToken) {
setIdToken(newIdToken);
setAccessToken(newIdToken);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how getToken is defined in the microapp bridge and whether it provides both tokens
rg -n "getToken" --type=ts -C5

Repository: wso2-open-operations/cs-tools

Length of output: 3227


🏁 Script executed:

cat -n apps/customer-portal/microapp/src/services/auth.ts | head -60

Repository: wso2-open-operations/cs-tools

Length of output: 2853


🏁 Script executed:

rg -n "setAccessToken|setIdToken" --type=ts -C3

Repository: wso2-open-operations/cs-tools

Length of output: 1792


Both ID token and access token are being set to the same value.

The native bridge's getToken function only supplies a single token (referred to as newIdToken in the callback parameter), which is then assigned to both setIdToken and setAccessToken. In standard OAuth2/OIDC flows, these are distinct tokens with different purposes. Either provide separate tokens from the bridge or document this as an intentional design decision.

🤖 Prompt for AI Agents
In `@apps/customer-portal/microapp/src/services/auth.ts` around lines 46 - 48, The
callback passed to the native bridge's getToken is incorrectly treating a single
token as both an ID token and an access token: it calls setIdToken(newIdToken)
and setAccessToken(newIdToken). Fix by updating the bridge and caller so
getToken returns distinct tokens (e.g., { idToken, accessToken }) and then call
setIdToken(idToken) and setAccessToken(accessToken); if you cannot change the
bridge immediately, only call setIdToken(newIdToken) (remove setAccessToken) and
add a TODO comment and a logging/validation path to surface that accessToken is
missing. Update the parameter name (newIdToken -> { idToken, accessToken } or
newToken) and adjust any consumers accordingly.

Comment on lines +73 to +86
export const checkUserGroups = (groupNames: string | string[]): boolean => {
const token = getIdToken();

if (!token) {
Logger.error("ID token not found for group check.");
return false;
}

const decoded = jwtDecode<TokenPayload>(token);
const userGroups = decoded.groups ?? [];
const requiredGroups = Array.isArray(groupNames) ? groupNames : [groupNames];

return requiredGroups.some((group) => userGroups.includes(group));
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

checkUserGroups does not guard against malformed tokens.

jwtDecode throws on invalid/malformed JWTs. Unlike decodeTokenAndStoreUser, this function has no try/catch, so a corrupted token will propagate an unhandled exception to the caller.

🛡️ Proposed fix: wrap in try/catch
 export const checkUserGroups = (groupNames: string | string[]): boolean => {
   const token = getIdToken();
 
   if (!token) {
     Logger.error("ID token not found for group check.");
     return false;
   }
 
-  const decoded = jwtDecode<TokenPayload>(token);
-  const userGroups = decoded.groups ?? [];
-  const requiredGroups = Array.isArray(groupNames) ? groupNames : [groupNames];
+  try {
+    const decoded = jwtDecode<TokenPayload>(token);
+    const userGroups = decoded.groups ?? [];
+    const requiredGroups = Array.isArray(groupNames) ? groupNames : [groupNames];
 
-  return requiredGroups.some((group) => userGroups.includes(group));
+    return requiredGroups.some((group) => userGroups.includes(group));
+  } catch (error) {
+    Logger.error("Failed to decode token for group check", error);
+    return false;
+  }
 };
📝 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
export const checkUserGroups = (groupNames: string | string[]): boolean => {
const token = getIdToken();
if (!token) {
Logger.error("ID token not found for group check.");
return false;
}
const decoded = jwtDecode<TokenPayload>(token);
const userGroups = decoded.groups ?? [];
const requiredGroups = Array.isArray(groupNames) ? groupNames : [groupNames];
return requiredGroups.some((group) => userGroups.includes(group));
};
export const checkUserGroups = (groupNames: string | string[]): boolean => {
const token = getIdToken();
if (!token) {
Logger.error("ID token not found for group check.");
return false;
}
try {
const decoded = jwtDecode<TokenPayload>(token);
const userGroups = decoded.groups ?? [];
const requiredGroups = Array.isArray(groupNames) ? groupNames : [groupNames];
return requiredGroups.some((group) => userGroups.includes(group));
} catch (error) {
Logger.error("Failed to decode token for group check", error);
return false;
}
};
🤖 Prompt for AI Agents
In `@apps/customer-portal/microapp/src/services/auth.ts` around lines 73 - 86, The
checkUserGroups function currently calls jwtDecode without handling
malformed/invalid tokens; wrap the jwtDecode call (and any dependent logic using
decoded.groups) in a try/catch block, mirroring the pattern used in
decodeTokenAndStoreUser: on error catch and Logger.error the exception
(including context such as the token presence) and return false so exceptions
don't propagate; ensure you still return false when token is missing or decoding
fails and keep using getIdToken, jwtDecode, and the requiredGroups/userGroups
logic as before.

Comment on lines +93 to +117
export const decodeTokenAndStoreUser = (): User | null => {
try {
const token = getIdToken();

if (!token) {
Logger.error("ID token not found for user decoding.");
useUserStore.getState().clearUser();
return null;
}

const decoded = jwtDecode<TokenPayload>(token);

// Extract user information from token
const user: User = {
email: decoded.email || "",
name: `${decoded.given_name || ""} ${decoded.family_name || ""}`,
};

return user;
} catch (error) {
Logger.error("Failed to decode token and store user information", error);
useUserStore.getState().clearUser();
return null;
}
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cat -n apps/customer-portal/microapp/src/services/auth.ts | head -150

Repository: wso2-open-operations/cs-tools

Length of output: 5483


🏁 Script executed:

rg "setUser" --type ts -A 3 -B 3

Repository: wso2-open-operations/cs-tools

Length of output: 1001


🏁 Script executed:

rg "useUserStore" --type ts -A 5 -B 2

Repository: wso2-open-operations/cs-tools

Length of output: 3420


decodeTokenAndStoreUser constructs a user object but never stores it in the Zustand store.

The function name and JSDoc indicate it should store user information, but useUserStore.getState().setUser(user) is never called (line 111 only returns the user). Additionally, the return value is discarded when called from initializeUserFromToken at line 127. This means the user state remains uninitialized—it is only ever cleared on error paths, never set on success.

Add the missing call:

Proposed fix
    const user: User = {
      email: decoded.email || "",
      name: `${decoded.given_name || ""} ${decoded.family_name || ""}`,
    };

+   useUserStore.getState().setUser(user);
+
    return user;
📝 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
export const decodeTokenAndStoreUser = (): User | null => {
try {
const token = getIdToken();
if (!token) {
Logger.error("ID token not found for user decoding.");
useUserStore.getState().clearUser();
return null;
}
const decoded = jwtDecode<TokenPayload>(token);
// Extract user information from token
const user: User = {
email: decoded.email || "",
name: `${decoded.given_name || ""} ${decoded.family_name || ""}`,
};
return user;
} catch (error) {
Logger.error("Failed to decode token and store user information", error);
useUserStore.getState().clearUser();
return null;
}
};
export const decodeTokenAndStoreUser = (): User | null => {
try {
const token = getIdToken();
if (!token) {
Logger.error("ID token not found for user decoding.");
useUserStore.getState().clearUser();
return null;
}
const decoded = jwtDecode<TokenPayload>(token);
// Extract user information from token
const user: User = {
email: decoded.email || "",
name: `${decoded.given_name || ""} ${decoded.family_name || ""}`,
};
useUserStore.getState().setUser(user);
return user;
} catch (error) {
Logger.error("Failed to decode token and store user information", error);
useUserStore.getState().clearUser();
return null;
}
};
🤖 Prompt for AI Agents
In `@apps/customer-portal/microapp/src/services/auth.ts` around lines 93 - 117,
The function decodeTokenAndStoreUser currently builds a User object but never
writes it to the Zustand store; update decodeTokenAndStoreUser to call
useUserStore.getState().setUser(user) after constructing the user (before
returning) so the parsed user is persisted, and ensure error paths still call
useUserStore.getState().clearUser() as they already do; reference:
decodeTokenAndStoreUser, useUserStore.getState().setUser,
useUserStore.getState().clearUser, getIdToken, jwtDecode.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@apps/customer-portal/microapp/src/components/features/create/Field.tsx`:
- Around line 20-22: In SelectField, avoid throwing inside the render by moving
the duplicate-options check behind a dev-only guard and memoizing the
computation: compute values from the options prop with React.useMemo
(referencing options and the derived values variable) and only run the
uniqueness check when process.env.NODE_ENV !== 'production'; if duplicates are
found, log a console.warn (or processLogger.warn) with context instead of
throwing so the UI won't unmount. Ensure you remove the unconditional new
Set(values) allocation from the render path and keep the runtime behavior
unchanged in production.
🧹 Nitpick comments (1)
apps/customer-portal/microapp/src/components/features/create/Field.tsx (1)

74-74: Move lineHeight to slotProps.input.sx for proper styling of the textarea element.

The sx prop on MuiTextField targets the root FormControl wrapper, not the inner <input>/<textarea>. When multiline={true}, the lineHeight value on the root won't reach the textarea text. Move it into slotProps.input instead:

Suggested adjustment
         sx={{ bgcolor: "background.paper" }}
         slotProps={{
           input: {
             startAdornment: startAdornment,
+            sx: { lineHeight: multiline ? 1.65 : undefined },
           },
         }}

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

Labels

Projects

Development

Successfully merging this pull request may close these issues.

3 participants