[Customer Portal][FE][Web] Integrate Rich Text Editor into Case Activity and Refine App Shell UI - #177
Conversation
Temporarily comment out several navigation items in apps/customer-portal/webapp/src/constants/appLayoutConstants.ts (engagements, legal-contracts, and community). This hides these entries from the app shell navigation (wrapped in a block comment) while those features or labels are pending changes or cleanup.
Remove explicit `border`, `borderRadius`, and `boxShadow` overrides from the ChatMessageCard Box styles in apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ChatMessageCard.tsx. This cleans up styling so the component uses the theme/default container styles instead of forced overrides, improving visual consistency and simplifying the component.
Swap the plain TextField for the rich-text Editor component to allow formatted comments. Add a resetTrigger state to clear the editor after a successful post and adjust the empty-check to treat HTML placeholders (e.g. "<p><br></p>") as empty. Move layout to a column with the Editor in a relative box and overlay the send IconButton in the bottom-right with updated styling. Preserve pending state visuals with CircularProgress and keep authentication/disabled logic intact. Also tidy imports to remove TextField and add Editor.
Comment out the SubscriptionWidget in SideBar.tsx to remove it from the sidebar footer UI. This temporarily hides the subscription component; re-enable it by uncommenting the line. Affected file: apps/customer-portal/webapp/src/components/common/side-nav-bar/SideBar.tsx.
Introduce an internal ResetPlugin and a resetTrigger prop to allow externally clearing the editor (editor root is cleared when resetTrigger > 0). Add configurable minHeight (number or string) with proper px conversion and a showToolbar boolean to conditionally render the toolbar and divider. Wire the ResetPlugin into the plugin chain and propagate the new props through the Editor component to support layout and reset control.
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📝 WalkthroughWalkthroughAdds a ResetPlugin and new props (resetTrigger, minHeight, showToolbar) to the rich-text Editor; replaces a plain TextField with the Editor in ActivityCommentInput (with send overlay, validation, and reset on success); comments out two nav items and the SubscriptionWidget; minor Paper style removal in ChatMessageCard. Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 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 refines the customer portal Activity UI by switching the activity comment input to the shared rich-text editor component, adjusting message card styling, and temporarily disabling some navigation/sidebar items.
Changes:
- Replaced the activity comment plain text input with the shared Lexical-based
Editor, adding editor reset support after successful sends. - Adjusted chat message card styling by removing explicit border/radius/shadow overrides.
- Commented out the Subscription widget and several app shell nav items.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/customer-portal/webapp/src/constants/appLayoutConstants.ts | Commented out several nav items (currently causes unused icon imports). |
| apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ChatMessageCard.tsx | Removed explicit Paper border/radius/shadow styling overrides. |
| apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx | Switched to rich-text Editor, added reset trigger, updated send behavior. |
| apps/customer-portal/webapp/src/components/common/side-nav-bar/SideBar.tsx | Commented out SubscriptionWidget in the sidebar footer (currently leaves an unused import). |
| apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx | Added ResetPlugin, and new minHeight / showToolbar props for better reuse. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
apps/customer-portal/webapp/src/components/common/side-nav-bar/SideBar.tsx (1)
21-21:SubscriptionWidgetis imported but no longer used.Line 21 imports
SubscriptionWidget, but the only usage (Line 83) is commented out. Consider removing the import to keep the module clean, or add a brief comment on when/why it will be re-enabled.Also applies to: 83-83
🤖 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/side-nav-bar/SideBar.tsx` at line 21, The import of SubscriptionWidget in SideBar.tsx is unused (its only usage is commented out); remove the line importing SubscriptionWidget or, if you intend to re-enable the widget soon, keep the import but add a short comment near the commented-out JSX explaining why it's disabled and when to re-enable it. Update the import statement referencing SubscriptionWidget accordingly and ensure no lint errors remain from the unused import.apps/customer-portal/webapp/src/constants/appLayoutConstants.ts (1)
54-66: Consider removing commented-out items or adding a TODO with a tracking issue.Block-commenting navigation items leaves dead code and unused imports (
Briefcase,FileText,Users). If these features are expected back soon, a short// TODO(JIRA-XXX): re-enable when readywould clarify intent; otherwise, prefer deleting and recovering from git history.Also note: the block comment disables three items (Engagements, Legal Contracts, and Community), though the PR description only mentions two.
,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/constants/appLayoutConstants.ts` around lines 54 - 66, The commented-out navigation items ("engagements", "legal-contracts", "community") and their icons (Briefcase, FileText, Users) are dead code; either delete the entire commented block or replace it with a one-line TODO noting a tracking ticket (e.g., // TODO(JIRA-XXX): re-enable engagements/legal-contracts/community) so intent is clear; also remove the now-unused imports Briefcase, FileText, Users from the top of the file to eliminate linter warnings and keep appLayoutConstants' nav array clean (locate the commented block in appLayoutConstants where the ids and icon names appear).apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx (3)
102-103: Duplicated empty-check logic withhandleSend.The disabled condition duplicates the empty-content check from
handleSend(lines 49-52). Extract a sharedisEffectivelyEmptyderived value (e.g., viauseMemo) to keep the logic in one place.♻️ Suggested approach
+ const isEffectivelyEmpty = useMemo(() => { + const trimmed = value.trim(); + return !trimmed || isHtmlEmpty(trimmed); + }, [value]); + // Then use in handleSend: // if (isEffectivelyEmpty || isDisabled) return; // And in the button: - disabled={!value.trim() || value === "<p><br></p>" || isDisabled} + disabled={isEffectivelyEmpty || isDisabled}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx` around lines 102 - 103, The disabled check on the IconButton duplicates the empty-content logic in handleSend; extract a shared derived boolean (e.g., isEffectivelyEmpty) using useMemo and use that variable in both the IconButton disabled prop and inside handleSend to decide early-return/send behavior; locate the logic in ActivityCommentInput (referencing the IconButton usage and the handleSend function) and replace the duplicated checks (value.trim() and value === "<p><br></p>") with the single isEffectivelyEmpty memoized value.
49-52: Fragile empty-content check — Lexical's empty HTML can vary.Hardcoding
"<p><br></p>"is brittle. Lexical may serialize empty state differently across versions or configurations (e.g.,<p><br/></p>,<p class="editor-paragraph"><br></p>). Consider stripping HTML tags and checking if the remaining text content is empty:♻️ Suggested helper
+ const isHtmlEmpty = (html: string): boolean => { + const text = html.replace(/<[^>]*>/g, "").trim(); + return text.length === 0; + }; + const handleSend = () => { const trimmedValue = value.trim(); - const isEffectivelyEmpty = !trimmedValue || trimmedValue === "<p><br></p>"; + const isEffectivelyEmpty = !trimmedValue || isHtmlEmpty(trimmedValue); if (isEffectivelyEmpty || isDisabled) return;This helper can also be reused for the button's
disabledprop on line 103.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx` around lines 49 - 52, Replace the brittle literal check for "<p><br></p>" in ActivityCommentInput with a reusable helper (e.g., isHtmlEmpty or getTextFromHtml) that strips HTML tags and decodes entities (or uses DOMParser to obtain textContent) and returns true when the resulting text is empty; use that helper to compute isEffectivelyEmpty (instead of comparing trimmedValue to the hardcoded string) and reuse it for the comment submit button's disabled prop so both the early return and the button state rely on the same robust empty-content check.
83-91:valueprop to Editor is only used as initial HTML — naming may confuse future readers.The Editor's
valueprop feedsInitialValuePlugin, which only applies on first render. After that, the editor is the source of truth andonChangepushes HTML back to state. This is fine for the current use case, but passing the continuously-updatedvaluestate back as a prop creates an illusion of a controlled component. A comment clarifying this would help.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx` around lines 83 - 91, The Editor's value prop is only consumed by InitialValuePlugin on first render (so the component is effectively uncontrolled thereafter), which can mislead readers; update ActivityCommentInput.tsx to add a concise inline comment above the Editor usage (near the value={value}, onChange={setValue} lines) stating that value is an initial HTML only, that InitialValuePlugin applies it once, the editor maintains its own state afterwards, and onChange pushes HTML back into local state; optionally consider renaming the prop to initialValue if you want the code to reflect this behavior, but at minimum add the explanatory comment referencing Editor, InitialValuePlugin, value, and onChange.apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx (1)
256-263: DuplicateminHeighton container and inner.editor-input— verify intent.
minHeightis applied both to the outer<Box>(line 259) and to.editor-input(lines 262-263). The outer Box also contains the placeholder Typography and all plugins. If the intent is to set minimum editor height, applying it only to.editor-inputshould suffice. The double application shouldn't break anything, but it may cause unexpected layout when the toolbar is hidden (showToolbar=false) since the outer Box min-height stays the same regardless.🤖 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/rich-text-editor/Editor.tsx` around lines 256 - 263, The outer Box and the inner `.editor-input` both receive the same `minHeight`, which is redundant and can cause layout issues when `showToolbar` toggles; decide whether minimum height should apply to the container (Box) or only to the editor content (`.editor-input`) and remove the duplicate. Locate the Box JSX (around the component render) and either remove the `minHeight` from the outer Box's sx (leaving it on `.editor-input`) if you want only the editor area to control height, or remove the `.editor-input` minHeight and keep the Box minHeight if the whole container (including placeholder and plugins) must maintain height; update any conditional logic tied to `showToolbar` to ensure the chosen element receives the correct computed `minHeight` value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx`:
- Around line 57-59: The explicit call to setValue("") in the onSuccess handler
is immediately overwritten by OnChangePlugin when ResetPlugin clears the editor,
so either remove setValue("") to avoid confusion or keep it but add a brief
clarifying comment; update the onSuccess block (the handler that calls setValue
and setResetTrigger) to either delete the setValue("") call or annotate it
(mentioning OnChangePlugin/ResetPlugin will repopulate the value with Lexical's
empty serialization like "<p><br></p>") so future readers understand why the
call is redundant or intentionally retained.
---
Nitpick comments:
In
`@apps/customer-portal/webapp/src/components/common/rich-text-editor/Editor.tsx`:
- Around line 256-263: The outer Box and the inner `.editor-input` both receive
the same `minHeight`, which is redundant and can cause layout issues when
`showToolbar` toggles; decide whether minimum height should apply to the
container (Box) or only to the editor content (`.editor-input`) and remove the
duplicate. Locate the Box JSX (around the component render) and either remove
the `minHeight` from the outer Box's sx (leaving it on `.editor-input`) if you
want only the editor area to control height, or remove the `.editor-input`
minHeight and keep the Box minHeight if the whole container (including
placeholder and plugins) must maintain height; update any conditional logic tied
to `showToolbar` to ensure the chosen element receives the correct computed
`minHeight` value.
In `@apps/customer-portal/webapp/src/components/common/side-nav-bar/SideBar.tsx`:
- Line 21: The import of SubscriptionWidget in SideBar.tsx is unused (its only
usage is commented out); remove the line importing SubscriptionWidget or, if you
intend to re-enable the widget soon, keep the import but add a short comment
near the commented-out JSX explaining why it's disabled and when to re-enable
it. Update the import statement referencing SubscriptionWidget accordingly and
ensure no lint errors remain from the unused import.
In
`@apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx`:
- Around line 102-103: The disabled check on the IconButton duplicates the
empty-content logic in handleSend; extract a shared derived boolean (e.g.,
isEffectivelyEmpty) using useMemo and use that variable in both the IconButton
disabled prop and inside handleSend to decide early-return/send behavior; locate
the logic in ActivityCommentInput (referencing the IconButton usage and the
handleSend function) and replace the duplicated checks (value.trim() and value
=== "<p><br></p>") with the single isEffectivelyEmpty memoized value.
- Around line 49-52: Replace the brittle literal check for "<p><br></p>" in
ActivityCommentInput with a reusable helper (e.g., isHtmlEmpty or
getTextFromHtml) that strips HTML tags and decodes entities (or uses DOMParser
to obtain textContent) and returns true when the resulting text is empty; use
that helper to compute isEffectivelyEmpty (instead of comparing trimmedValue to
the hardcoded string) and reuse it for the comment submit button's disabled prop
so both the early return and the button state rely on the same robust
empty-content check.
- Around line 83-91: The Editor's value prop is only consumed by
InitialValuePlugin on first render (so the component is effectively uncontrolled
thereafter), which can mislead readers; update ActivityCommentInput.tsx to add a
concise inline comment above the Editor usage (near the value={value},
onChange={setValue} lines) stating that value is an initial HTML only, that
InitialValuePlugin applies it once, the editor maintains its own state
afterwards, and onChange pushes HTML back into local state; optionally consider
renaming the prop to initialValue if you want the code to reflect this behavior,
but at minimum add the explanatory comment referencing Editor,
InitialValuePlugin, value, and onChange.
In `@apps/customer-portal/webapp/src/constants/appLayoutConstants.ts`:
- Around line 54-66: The commented-out navigation items ("engagements",
"legal-contracts", "community") and their icons (Briefcase, FileText, Users) are
dead code; either delete the entire commented block or replace it with a
one-line TODO noting a tracking ticket (e.g., // TODO(JIRA-XXX): re-enable
engagements/legal-contracts/community) so intent is clear; also remove the
now-unused imports Briefcase, FileText, Users from the top of the file to
eliminate linter warnings and keep appLayoutConstants' nav array clean (locate
the commented block in appLayoutConstants where the ids and icon names appear).
Update comments in ActivityCommentInput to reflect use of the shared rich-text Editor and that comments are posted via usePostComment. Add an inline note explaining that the local value is reset immediately on success for UI responsiveness, while ResetPlugin will clear the Lexical editor later and OnChangeHTMLPlugin may repopulate the value with an empty paragraph.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/customer-portal/webapp/src/components/support/case-details/activity-tab/ActivityCommentInput.tsx`:
- Around line 86-125: The send button (IconButton) is absolutely positioned and
can overlap Editor content; update the container or the Editor content area to
add bottom spacing (e.g., paddingBottom ~44px) so typed text is never obscured.
Modify the Box wrapping the Editor (or pass a style/prop into the Editor
component) to include paddingBottom and ensure the absolute-positioned
IconButton (referenced in this file) still sits at bottom: 8 without covering
text; keep existing minHeight and disabled logic (value, setValue, resetTrigger,
handleSend, postComment) unchanged.
- Around line 49-52: The empty-content check in ActivityCommentInput is fragile
and duplicated: replace the inline checks with a shared helper (e.g.,
isEffectivelyEmptyContent) that strips HTML tags (remove all <...>), trims the
resulting text, and returns true if there's no non-whitespace text; call this
helper from handleSend and from the comment send button's disabled prop so both
use the same logic and avoid inconsistencies between trimmedValue and raw value.
Expose a placeholder prop on the rich-text Editor and pass a default prompt from ActivityCommentInput so the comment editor shows "Write a comment...". Also cleaned up a couple of redundant inline comments around empty-content checks and reset behavior. Affects Editor.tsx and ActivityCommentInput.tsx.
Import stripHtml and use it to determine if the editor content is effectively empty (covers HTML-only content like <p><br></p>). Replace the previous ad-hoc checks with stripHtml(value).length === 0, trim the value when sending, and update the IconButton disabled condition accordingly. This simplifies empty-content detection and avoids false-positives from HTML markup.
cea6667
into
wso2-open-operations:customer-portal-milestone-1
Description
This pull request introduces several improvements to the rich text editor used for activity comments, enhances the comment input UX, and includes minor UI updates and code cleanup. The most significant change is the integration of a custom
Editorcomponent for comment input, supporting content reset and improved validation. Additionally, there are updates to how toolbars and minimum heights are handled in the editor, and some UI elements and navigation items are commented out for future consideration.Rich Text Editor Enhancements:
ResetPluginto theEditorcomponent to allow external triggers to clear the editor content, supporting the newresetTriggerprop. (Editor.tsx)Editorcomponent to accept new props:resetTrigger,minHeight, andshowToolbar, enabling more flexible usage and configuration. (Editor.tsx)Editor.tsx)ResetPlugininto the editor's plugin stack to respond to reset events. (Editor.tsx)Activity Comment Input Refactor:
TextFieldwith the newEditorcomponent inActivityCommentInput, enabling rich text input and resetting the editor after successful comment submission. (ActivityCommentInput.tsx) [1] [2] [3] [4]<p><br></p>) and prevent sending empty comments. (ActivityCommentInput.tsx)UI and Navigation Updates:
SubscriptionWidgetin the sidebar footer, possibly for redesign or removal. (SideBar.tsx)appLayoutConstants.ts) [1] [2]Minor UI Tweaks:
ChatMessageCardfor a cleaner appearance. (ChatMessageCard.tsx)Summary by CodeRabbit
New Features
Style
Chores