[Customer Portal Micro App] Initial Frontend Architecture and Core UI Implementation#9
Conversation
…loading due to incomplete config support for layout
… inside components directory
…o improve modularity
…ch support using url params
… case-create handoff
…port variant-based rendering
…ult blue text on ios
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds 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
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 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.
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 onPieDataItem.The
[key: string]: string | numberindex 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 usingitem.labelas the key instead of array index.If labels are guaranteed unique within a dataset (which they should be for a meaningful pie chart),
item.labelis 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:messageslacks a type annotation — falls back toany.
location.stateisunknown(React Router v6), somessagesis implicitlyany. An explicit type guard or cast toChatMessage[](the typeConversationSummaryexpects) would catch shape mismatches at compile time.Suggested fix
- const messages = location.state?.messages || []; + const messages: ChatMessage[] = (location.state as { messages?: ChatMessage[] })?.messages ?? [];(Import
ChatMessagefrom 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
validationSchemais added, the current JSX never readsformik.errorsorformik.touched, so users would get no inline feedback on invalid input. Worth wiring uperror/helperTextprops on the field components when validation is introduced.apps/customer-portal/microapp/src/pages/EditUserPage.tsx (1)
83-92: LocalSectionCardduplicates@components/shared/SectionCard.tsx.A shared
SectionCardalready exists (with slightly different spacing/typography). Consider reusing it to avoid divergence.apps/customer-portal/microapp/src/services/auth.ts (1)
24-30:given_nameandfamily_nameare marked as required but may be absent in the JWT.Standard OIDC claims like
given_nameandfamily_nameare optional. If the token doesn't include them,jwtDecodewon't throw — they'll just beundefinedat 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: Strayconsole.erroralongsideLogger.error.Line 153 already logs via
Logger.error. Theconsole.erroron 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: TherefreshTokenPromisemodule-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.
apps/customer-portal/microapp/src/components/features/chat/MessageBubble.tsx
Show resolved
Hide resolved
apps/customer-portal/microapp/src/components/features/projects/ProjectCard.tsx
Show resolved
Hide resolved
apps/customer-portal/microapp/src/components/features/projects/ProjectCard.tsx
Outdated
Show resolved
Hide resolved
| 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"); | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Hardcoded test data and alert() should be removed before merge.
- Lines 72–74: The initial
titleanddescriptioncontain 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(orvalidate) 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.
| 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, | ||
| }); |
There was a problem hiding this comment.
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.
| 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]").
| 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; |
There was a problem hiding this comment.
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.
| 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.
| if (newIdToken) { | ||
| setIdToken(newIdToken); | ||
| setAccessToken(newIdToken); |
There was a problem hiding this comment.
🧩 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 -C5Repository: wso2-open-operations/cs-tools
Length of output: 3227
🏁 Script executed:
cat -n apps/customer-portal/microapp/src/services/auth.ts | head -60Repository: wso2-open-operations/cs-tools
Length of output: 2853
🏁 Script executed:
rg -n "setAccessToken|setIdToken" --type=ts -C3Repository: 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.
| 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)); | ||
| }; |
There was a problem hiding this comment.
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.
| 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.
| 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; | ||
| } | ||
| }; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n apps/customer-portal/microapp/src/services/auth.ts | head -150Repository: wso2-open-operations/cs-tools
Length of output: 5483
🏁 Script executed:
rg "setUser" --type ts -A 3 -B 3Repository: wso2-open-operations/cs-tools
Length of output: 1001
🏁 Script executed:
rg "useUserStore" --type ts -A 5 -B 2Repository: 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.
| 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.
There was a problem hiding this comment.
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: MovelineHeighttoslotProps.input.sxfor proper styling of the textarea element.The
sxprop onMuiTextFieldtargets the rootFormControlwrapper, not the inner<input>/<textarea>. Whenmultiline={true}, thelineHeightvalue on the root won't reach the textarea text. Move it intoslotProps.inputinstead:Suggested adjustment
sx={{ bgcolor: "background.paper" }} slotProps={{ input: { startAdornment: startAdornment, + sx: { lineHeight: multiline ? 1.65 : undefined }, }, }}
apps/customer-portal/microapp/src/components/features/create/Field.tsx
Outdated
Show resolved
Hide resolved
…ate ui for consistency
…factor tab titles into constant




















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
Documentation
Chores