Skip to content

feat: add avatar crop modal with zoom and pan support#39084

Open
himanshu6093 wants to merge 9 commits intoRocketChat:developfrom
himanshu6093:feat/avatar-cropper
Open

feat: add avatar crop modal with zoom and pan support#39084
himanshu6093 wants to merge 9 commits intoRocketChat:developfrom
himanshu6093:feat/avatar-cropper

Conversation

@himanshu6093
Copy link

@himanshu6093 himanshu6093 commented Feb 26, 2026

Proposed changes (including videos or screenshots)

This PR adds a crop modal to the user avatar upload flow. When a user selects an image for their profile picture, a modal now appears allowing them to:

Zoom the image using a slider (50%–300%)
Pan the image by clicking and dragging
Preview the final result through a circular crop window
The cropped image is then uploaded as a file via the existing users.setAvatar endpoint.

Technical changes
New file:
AvatarCropModal.tsx

  • Canvas-based image rendering with zoom and pan controls.
  • Uses existing Fuselage modal components (Modal, Slider, Icon, ModalBackdrop)
  • Exports the visible 300×300 crop area as a PNG data URL

Modified:
UserAvatarEditor.tsx

  • Intercepts file selection to show the crop modal instead of setting the avatar directly
  • Passes the cropped result as an avatarUrl to the form controller
  • Uses useSingleFileInput reset to allow re-selecting the same file

Modified:
useUpdateAvatar.ts

  • Added dataUriToFile() helper to convert base64 data URIs to useSingleFileInput.ts
  • When avatarUrl is a data URI, converts it to FormData and uploads via the file upload endpoint instead of sending the raw base64 string

Before:
https://github.com/user-attachments/assets/b9535d12-95cd-467b-8a15-60500ca27a74

After:
https://github.com/user-attachments/assets/2307640d-0d37-4399-8ee7-0bbc8d1949ad

Issue(s)

Closes #39055

Steps to test or reproduce

  1. Log in and navigate to Profile settings (/account/profile)
  2. Click the Upload button in the avatar section
  3. Select any image file
  4. The Crop Image modal should appear with a circular preview
  5. Use the zoom slider and drag to position the image
  6. Click Apply — the cropped preview should appear in the avatar area
  7. The Save changes button should appear at the bottom
  8. Click Save changes — the avatar should update successfully
  9. Verify: refresh the page, the new avatar should persist

Further comments

I built a custom crop modal using HTML5 Canvas instead of adding a third-party cropping library, to avoid increasing the bundle size. It uses existing Fuselage components for the UI, so it fits naturally with the rest of the app.

Summary by CodeRabbit

  • New Features

    • Interactive avatar crop modal: circular 300×300 preview with zoom slider, drag-to-pan, Apply/Cancel actions and localized title.
    • Cropped avatar exported as high-resolution PNG and applied to the user profile when confirmed.
  • Bug Fixes / Improvements

    • Uploaded images now open in the crop modal before saving; state resets and null checks improve robustness.
    • Supports avatar inputs delivered as data URIs so pasted/embedded images are handled smoothly.

@himanshu6093 himanshu6093 requested a review from a team as a code owner February 26, 2026 08:46
@dionisio-bot
Copy link
Contributor

dionisio-bot bot commented Feb 26, 2026

Looks like this PR is not ready to merge, because of the following issues:

  • This PR is missing the 'stat: QA assured' label
  • This PR is missing the required milestone or project

Please fix the issues and try again

If you have any trouble, please check the PR guidelines

@CLAassistant
Copy link

CLAassistant commented Feb 26, 2026

CLA assistant check
All committers have signed the CLA.

@changeset-bot
Copy link

changeset-bot bot commented Feb 26, 2026

🦋 Changeset detected

Latest commit: d34fbc1

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 41 packages
Name Type
@rocket.chat/meteor Minor
@rocket.chat/core-typings Minor
@rocket.chat/rest-typings Minor
@rocket.chat/uikit-playground Patch
@rocket.chat/api-client Patch
@rocket.chat/apps Patch
@rocket.chat/core-services Patch
@rocket.chat/cron Patch
@rocket.chat/ddp-client Patch
@rocket.chat/fuselage-ui-kit Major
@rocket.chat/gazzodown Major
@rocket.chat/http-router Patch
@rocket.chat/livechat Patch
@rocket.chat/model-typings Patch
@rocket.chat/ui-avatar Major
@rocket.chat/ui-client Major
@rocket.chat/ui-contexts Major
@rocket.chat/ui-voip Major
@rocket.chat/web-ui-registration Major
@rocket.chat/account-service Patch
@rocket.chat/authorization-service Patch
@rocket.chat/ddp-streamer Patch
@rocket.chat/omnichannel-transcript Patch
@rocket.chat/presence-service Patch
@rocket.chat/queue-worker Patch
@rocket.chat/abac Patch
@rocket.chat/federation-matrix Patch
@rocket.chat/license Patch
@rocket.chat/media-calls Patch
@rocket.chat/omnichannel-services Patch
@rocket.chat/pdf-worker Patch
@rocket.chat/presence Patch
rocketchat-services Patch
@rocket.chat/models Patch
@rocket.chat/network-broker Patch
@rocket.chat/omni-core-ee Patch
@rocket.chat/mock-providers Patch
@rocket.chat/ui-video-conf Major
@rocket.chat/instance-status Patch
@rocket.chat/omni-core Patch
@rocket.chat/server-fetch Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2ecb1e3 and 710dc9d.

📒 Files selected for processing (1)
  • apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer

Walkthrough

Adds a new AvatarCropModal component (circular 300×300 crop viewport with pan/zoom and export) and integrates it into UserAvatarEditor to defer uploads until Apply; updates useUpdateAvatar to convert cropped data-URI avatars to File+FormData and call saveAvatarAction. Includes a changelog entry.

Changes

Cohort / File(s) Summary
Avatar Crop Modal
apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx
New React/TypeScript component rendering a circular 300×300 crop viewport, high‑resolution offscreen canvas, pointer drag-to-pan, zoom slider (50–300%), Apply exports 300×300 PNG data URI via onApply, Cancel via onCancel.
Avatar Editor Integration
apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx
Adds imageToCrop state, shows AvatarCropModal for uploaded images, defers applying avatar until modal onApply, and clears state on cancel; replaces immediate preview-to-avatar flow with crop-confirmation flow.
Avatar Update Hook
apps/meteor/client/hooks/useUpdateAvatar.ts
Adds dataUriToFile helper and branch: when avatar URL is a data URI, convert to File, wrap in FormData and call saveAvatarAction (instead of URL path).
Changelog
.changeset/add-avatar-crop-modal.md
New changeset documenting the addition of the avatar crop modal and associated flow changes.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UserAvatarEditor
    participant AvatarCropModal
    participant useUpdateAvatar
    participant Backend

    User->>UserAvatarEditor: Upload image
    UserAvatarEditor->>UserAvatarEditor: set imageToCrop
    UserAvatarEditor->>AvatarCropModal: render(imageSrc)
    User->>AvatarCropModal: pan/zoom interactions
    User->>AvatarCropModal: click Apply
    AvatarCropModal->>AvatarCropModal: export 300x300 PNG dataURI
    AvatarCropModal->>UserAvatarEditor: onApply(dataURI)
    UserAvatarEditor->>useUpdateAvatar: trigger update with dataURI
    useUpdateAvatar->>useUpdateAvatar: dataUriToFile -> FormData
    useUpdateAvatar->>Backend: saveAvatarAction(FormData)
    Backend-->>useUpdateAvatar: success
    useUpdateAvatar-->>UserAvatarEditor: updated
    UserAvatarEditor-->>User: display new avatar
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an avatar crop modal with zoom and pan support capabilities.
Linked Issues check ✅ Passed All coding objectives from issue #39055 are implemented: circular crop area, zoom slider (50-300%), click-drag pan, Cancel/Apply actions, and modal before upload.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the crop modal feature from #39055; no unrelated modifications detected.

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


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.

@coderabbitai coderabbitai bot added the type: feature Pull requests that introduces new feature label Feb 26, 2026
Copy link
Contributor

@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: 4

🤖 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/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx`:
- Around line 56-80: The effect is recreating a new Image() and rebinding
img.onload on every zoom/offset change which causes jank; instead, create and
cache the HTMLImageElement once when imageSrc changes (e.g. imageRef or
loadedImage in an effect that depends only on imageSrc), set its onload there,
and in the effect that depends on zoom and offset use the cached imageRef to
redraw to the canvas (no new Image creation). Ensure you handle cleanup of
onload and check imageRef.current exists before drawing (and optionally use
requestAnimationFrame to schedule the canvas redraw in the zoom/offset effect).
- Around line 34-49: Replace mouse-only handlers with pointer event handlers to
add touch support: rename onMouseDown -> onPointerDown, onMouseMove ->
onPointerMove, onMouseUp -> onPointerUp and onMouseLeave -> onPointerCancel (or
onPointerUp) and update all usages (e.g., JSX props) accordingly; in
onPointerDown call e.currentTarget.setPointerCapture(e.pointerId) after setting
setIsDragging(true) and initializing dragStart.current, then in onPointerMove
use the same logic that uses dragStart.current and setOffset, and in
onPointerUp/onPointerCancel call setIsDragging(false) and release capture if
needed. Ensure the handler signatures accept React.PointerEvent<HTMLDivElement>
instead of MouseEvent and keep references to setIsDragging, dragStart, and
setOffset unchanged.

In `@apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx`:
- Around line 35-49: The file input reset is only called on the success path
inside setUploadedPreview, so when readFileAsDataURL or isValidImageFormat
throws the input isn't cleared; update setUploadedPreview to always call
resetUpload (e.g., via a finally block or by invoking it in both success and
catch branches) after attempting to read/validate the file, keeping the existing
error handling that dispatches the toast in the catch; reference the
setUploadedPreview function and resetUpload, and ensure readFileAsDataURL and
isValidImageFormat calls remain unchanged.

In `@apps/meteor/client/hooks/useUpdateAvatar.ts`:
- Around line 15-19: The data-URI path can throw from atob() causing an uncaught
crash; update dataUriToFile(dataUri: string) to validate the input more strictly
(ensure header and base64 part exist, header matches /^data:([^;]+);base64$/ and
base64Data is non-empty) and have it throw a clear Error on invalid input, and
wrap the call site in useUpdateAvatar's data-URI branch (the block that
currently calls dataUriToFile) in a try-catch that dispatches the same error
toast used in the service-object path (so user sees a message) and prevents the
unhandled exception—identify the caller by the hook useUpdateAvatar and the
helper dataUriToFile when making these changes.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between afaabea and 599f288.

📒 Files selected for processing (3)
  • apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx
  • apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx
  • apps/meteor/client/hooks/useUpdateAvatar.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx
  • apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx
  • apps/meteor/client/hooks/useUpdateAvatar.ts
🧠 Learnings (2)
📚 Learning: 2025-11-17T15:07:13.273Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37398
File: packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx:357-363
Timestamp: 2025-11-17T15:07:13.273Z
Learning: In packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx, IconElement is a presentational, non-actionable element that does not require wrapping in AppIdProvider, similar to plain_text and mrkdwn renderers. Only actionable elements (those with actions, actionId, or interactive behavior) should be wrapped in AppIdProvider.

Applied to files:

  • apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx
📚 Learning: 2026-02-10T16:32:42.586Z
Learnt from: tassoevan
Repo: RocketChat/Rocket.Chat PR: 38528
File: apps/meteor/client/startup/roles.ts:14-14
Timestamp: 2026-02-10T16:32:42.586Z
Learning: In Rocket.Chat's Meteor client code, DDP streams use EJSON and Date fields arrive as Date objects; do not manually construct new Date() in stream handlers (for example, in sdk.stream()). Only REST API responses return plain JSON where dates are strings, so implement explicit conversion there if needed. Apply this guidance to all TypeScript files under apps/meteor/client to ensure consistent date handling in DDP streams and REST responses.

Applied to files:

  • apps/meteor/client/hooks/useUpdateAvatar.ts
🧬 Code graph analysis (1)
apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx (2)
packages/ui-contexts/src/index.ts (1)
  • useToastMessageDispatch (78-78)
packages/core-typings/src/IUser.ts (1)
  • AvatarObject (302-302)

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

4 issues found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx">

<violation number="1" location="apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx:43">
P2: `resetUpload()` is only called on the success path. If an exception is thrown (e.g., by `readFileAsDataURL` or `isValidImageFormat`), the file input is never reset, preventing the user from re-selecting the same file. Move `resetUpload()` to a `finally` block. Also, `resetUpload` is missing from the `useCallback` dependency array, which violates the React hooks exhaustive-deps rule and may cause the callback to reference a stale closure.</violation>
</file>

<file name="apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx">

<violation number="1" location="apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx:34">
P2: Using mouse events (`onMouseDown`, `onMouseMove`, `onMouseUp`, `onMouseLeave`) prevents the drag/pan functionality from working on touch devices. Replace with pointer events (`onPointerDown`, `onPointerMove`, `onPointerUp`, `onPointerCancel`) which handle both mouse and touch inputs. Add `e.currentTarget.setPointerCapture(e.pointerId)` in the down handler to ensure reliable drag tracking.</violation>

<violation number="2" location="apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx:63">
P2: Creating a new `Image()` on every zoom/offset change causes unnecessary image reloads during panning. Since the `useEffect` depends on `[imageSrc, zoom, offset]`, each drag movement triggers a new `Image()` instantiation and `onload` binding, which can cause visible jank. Load the image once when `imageSrc` changes (storing it in a ref), and use a separate effect for the canvas drawing that reads from the ref.</violation>

<violation number="3" location="apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx:66">
P2: The canvas draw effect has no cleanup/guard, so a slow previous Image load can still run its onload after imageSrc changes and overwrite the preview with stale content.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Contributor

@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

🧹 Nitpick comments (1)
apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx (1)

128-132: Remove unnecessary double type assertion.

The as unknown as AvatarObject cast is redundant. Since { avatarUrl: croppedImage } has type { avatarUrl: string }, which matches AvatarUrlObj (a member of the AvatarObject union), it's directly assignable without casting—as shown in line 56 with the same pattern using avatarFromUrl.

setAvatarObj({ avatarUrl: croppedImage });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx`
around lines 128 - 132, In the onApply handler for UserAvatarEditor, remove the
unnecessary double type assertion when setting the avatar object: instead of
casting the object literal to unknown then AvatarObject, assign a plain object
with avatarUrl set to croppedImage to setAvatarObj; update the onApply callback
that currently calls setImageToCrop, setNewAvatarSource and setAvatarObj(...) so
setAvatarObj receives a correctly-typed { avatarUrl: croppedImage } without any
"as unknown as AvatarObject" cast.
🤖 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/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx`:
- Around line 53-84: The draw effect can run before imageRef.current is set
because img.onload is async; fix by triggering a redraw when the image finishes
loading—e.g., in the first useEffect where you create img and set
imageRef.current, also set a small piece of state (like imageLoaded or
imageVersion) or call a redraw trigger so the second effect runs again; add that
state (imageLoaded/imageVersion) to the dependency array of the drawing
useEffect that reads canvasRef and imageRef, or invoke the draw logic directly
from img.onload to ensure ctx.drawImage runs after imageRef is populated (update
references: imageRef, canvasRef, the two useEffect hooks, setZoom/setOffset as
needed).

---

Nitpick comments:
In `@apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx`:
- Around line 128-132: In the onApply handler for UserAvatarEditor, remove the
unnecessary double type assertion when setting the avatar object: instead of
casting the object literal to unknown then AvatarObject, assign a plain object
with avatarUrl set to croppedImage to setAvatarObj; update the onApply callback
that currently calls setImageToCrop, setNewAvatarSource and setAvatarObj(...) so
setAvatarObj receives a correctly-typed { avatarUrl: croppedImage } without any
"as unknown as AvatarObject" cast.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 599f288 and 2ecb1e3.

📒 Files selected for processing (4)
  • .changeset/add-avatar-crop-modal.md
  • apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx
  • apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx
  • apps/meteor/client/hooks/useUpdateAvatar.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/meteor/client/hooks/useUpdateAvatar.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)

**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation

Files:

  • apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx
  • apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx
🧠 Learnings (3)
📚 Learning: 2026-02-24T19:36:55.089Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 38493
File: apps/meteor/tests/e2e/page-objects/fragments/home-content.ts:60-82
Timestamp: 2026-02-24T19:36:55.089Z
Learning: In RocketChat/Rocket.Chat e2e tests (apps/meteor/tests/e2e/page-objects/fragments/home-content.ts), thread message preview listitems do not have aria-roledescription="message", so lastThreadMessagePreview locator cannot be scoped to messageListItems (which filters for aria-roledescription="message"). It should remain scoped to page.getByRole('listitem') or mainMessageList.getByRole('listitem').

Applied to files:

  • apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx
📚 Learning: 2026-02-12T15:39:28.416Z
Learnt from: dougfabris
Repo: RocketChat/Rocket.Chat PR: 32703
File: apps/meteor/client/lib/chats/flows/uploadFiles.ts:52-58
Timestamp: 2026-02-12T15:39:28.416Z
Learning: In `apps/meteor/client/lib/chats/flows/uploadFiles.ts`, when E2E encryption is required but not allowed (e.g., `E2E_Enable_Encrypt_Files` setting is disabled), the function intentionally abandons the entire upload queue and displays a toast error. This fail-fast behavior prevents partial uploads when encryption requirements cannot be met and is the expected behavior, not a bug.

Applied to files:

  • apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx
📚 Learning: 2025-11-17T15:07:13.273Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 37398
File: packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx:357-363
Timestamp: 2025-11-17T15:07:13.273Z
Learning: In packages/fuselage-ui-kit/src/surfaces/FuselageSurfaceRenderer.tsx, IconElement is a presentational, non-actionable element that does not require wrapping in AppIdProvider, similar to plain_text and mrkdwn renderers. Only actionable elements (those with actions, actionId, or interactive behavior) should be wrapped in AppIdProvider.

Applied to files:

  • apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx
🧬 Code graph analysis (1)
apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx (2)
packages/ui-contexts/src/index.ts (1)
  • useToastMessageDispatch (78-78)
packages/core-typings/src/IUser.ts (1)
  • AvatarObject (302-302)
🔇 Additional comments (4)
apps/meteor/client/components/avatar/UserAvatarEditor/AvatarCropModal.tsx (1)

103-157: LGTM!

The modal structure is well-organized with proper Fuselage components. The circular crop preview with pointer events for touch/mouse support and the zoom slider provide a good UX.

apps/meteor/client/components/avatar/UserAvatarEditor/UserAvatarEditor.tsx (2)

124-134: LGTM on the crop modal integration.

The conditional rendering of AvatarCropModal and the flow of clearing imageToCrop on both cancel and apply paths is correct.


35-52: No changes needed. The dependency array is correct.

resetUpload is created using useEffectEvent() from '@rocket.chat/fuselage-hooks', which returns a stable callback reference that maintains the same identity across renders. Per React hook rules, stable dependencies don't need to be included in dependency arrays. The code follows best practices and requires no fixes.

Likely an incorrect or invalid review comment.

.changeset/add-avatar-crop-modal.md (1)

1-5: LGTM!

The changeset correctly documents the new feature with an appropriate minor version bump.

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

Labels

type: feature Pull requests that introduces new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[UX Improvement]: Add interactive image cropping and scaling for User Avatars

2 participants