Skip to content

Add profile picture upload for users and organizations#704

Merged
SvenVw merged 23 commits into
developmentfrom
FDM664
Jul 23, 2026
Merged

Add profile picture upload for users and organizations#704
SvenVw merged 23 commits into
developmentfrom
FDM664

Conversation

@BoraIneviNMI

@BoraIneviNMI BoraIneviNMI commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

New features

  • Users can now delete their current profile picture or upload a new one. Uploaded profile pictures are stored on Google Cloud Storage using the existing adapter functions for that. Before uploading, the users are able to crop the image to the part of it they want, using an easy-to-use ImageCropper applet.

Closes #664

Summary by CodeRabbit

  • New Features
    • Added profile picture editing with cropping, compression, upload/replace, and deletion.
    • Added organization logo management with validation, upload/replace, and deletion.
    • Added profile info editing (firstname/surname) with a “Bijwerken” shortcut and improved welcome/profile-completion upload support.
  • UI Enhancements / Style
    • Profile picture cropping UI now includes zoom controls.
    • Upload dropzones have consistent height, and their styling is more flexible.
  • Other Improvements
    • Signed, cache-controlled loading for profile pictures and logos; org header now displays the logo.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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 shared profile-picture validation, cropping, compression, GCS delivery, and deletion flows for users and organizations. Updates organization creation/settings, user profile editing, welcome completion, organization branding displays, and upload dropzone styling.

Changes

Profile picture foundations

Layer / File(s) Summary
Shared picture contracts and delivery
fdm-app/app/components/blocks/profile/..., fdm-app/app/lib/..., fdm-app/app/routes/api.profile-picture..., fdm-app/package.json
Adds crop schemas, avatar compression, existing-object detection, organization image metadata, signed profile-picture delivery, and required packages.
Picture editor and upload controls
fdm-app/app/components/custom/..., fdm-app/app/components/blocks/profile/profile-picture-manager.tsx, fdm-app/app/components/ui/slider.tsx, fdm-app/app/components/custom/dropzone.tsx
Adds image cropping, zooming, upload previews, multipart preparation, slider controls, and configurable dropzone sizing.
Organization picture and settings flow
fdm-app/app/components/blocks/organization/..., fdm-app/app/routes/organization.*.tsx
Adds organization logo creation, replacement, deletion, multipart validation, intent-based settings updates, and logo display.
User profile editing flow
fdm-app/app/components/blocks/profile/profile-info-*, fdm-app/app/routes/user.settings.profile*.tsx
Adds profile information and picture editing routes, forms, validation, upload, deletion, and navigation.
Welcome picture completion flow
fdm-app/app/routes/welcome.tsx, .changeset/gold-actors-type.md
Extends welcome completion with optional picture upload, cropping, social-image retention, removal handling, and release metadata.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ProfilePictureManager
  participant ProfileAction
  participant GCS
  User->>ProfilePictureManager: Select and crop image
  ProfilePictureManager->>ProfileAction: Submit cropped multipart data
  ProfileAction->>GCS: Upload image object
  ProfileAction->>ProfileAction: Update user or organization image reference
  ProfileAction->>GCS: Delete previous object
Loading

Possibly related PRs

Suggested labels: enhancement, fdm-app, dependencies

Suggested reviewers: svenvw

Poem

I’m a rabbit with a picture to crop,
Zooming it gently till edges stop.
Logos hop into view,
Old files bid adieu!
GCS keeps each portrait bright.

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The upload flows are present, but the issue պահանջs persisting object keys in auth fields; the summary shows API URLs with hashes instead. Store deterministic GCS object keys in user.image and organization.logo, and generate signed URLs in loaders as required.
Out of Scope Changes check ⚠️ Warning The PR also changes unrelated upload height and organization navigation UI in soil/mijnpercelen and the global organization layout. Remove or split out the unrelated UI/layout tweaks so this PR stays focused on profile picture and organization logo uploads.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding profile picture upload support for users and organizations.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch FDM664

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.

@codecov

codecov Bot commented Jul 17, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fdm-app/app/components/blocks/organization/form.tsx`:
- Around line 178-187: Add type="button" to the logo-removal Button that calls
setProfilePictureFiles([]), ensuring it only clears the selected files and does
not submit the surrounding form.

In `@fdm-app/app/components/blocks/profile/detect-existing.server.ts`:
- Around line 15-20: Update the URL-to-key logic in detect-existing.server.ts
around the types loop to remove the query string from imageUrl before extracting
the path suffix. Ensure the returned profile_picture_${type} key uses only the
pathname portion, so cache-busting parameters are excluded and the result
matches buildObjectKey output.

In `@fdm-app/app/components/blocks/profile/profile-info-form.tsx`:
- Around line 71-78: Remove the stray “Verwijderen” submit Button from the
profile info form, leaving only the “Opslaan” button for submitting the
firstname/surname update. Preserve the existing isSubmitting disabled state and
spinner behavior on the remaining button.
- Line 35: Remove the leftover console.log(form.formState.errors) statement from
the profile info form component, leaving the surrounding form rendering and
validation logic unchanged.

In `@fdm-app/app/components/custom/image-cropper.tsx`:
- Around line 176-179: The crop rectangle callback in the frame-position update
handler must use the newly computed nextX, nextY, and nextScale values rather
than the stale framePosition values. In the imageData reset useEffect, after
resetting to the default frame position, also compute and emit the corresponding
rectangle through onFrameRectangleChange so untouched newly selected images
submit accurate crop bounds.

In `@fdm-app/app/routes/api.profile-picture`.$type.$file_name.ts:
- Around line 42-44: Update the Cache-Control header in the profile-picture
route to use private caching or disable shared caching instead of public
caching. Preserve the existing max-age only if compatible with the chosen
private policy, ensuring session and principal authorization checks remain
enforced for every client.
- Around line 38-46: Update the response handling after generateSignedReadUrl in
the profile-picture route to use react-router’s redirect helper with the signed
URL, ensuring a Location header is emitted. Use a temporary redirect status such
as 302 or 307 rather than 301, while preserving the existing cache-control
behavior.

In `@fdm-app/app/routes/organization`.$slug.settings.tsx:
- Around line 255-266: Update the rollback logic in the organization settings
route’s deleteObject failure path to restore the logo unconditionally, removing
the invalid currentOrganization.metadata.data guard because metadata is raw JSON
there. Keep the existing auth.api.updateOrganization call and payload unchanged,
matching the unconditional rollback behavior of the update_profile_picture
branch.
- Around line 107-111: Gebruik in de ProfilePictureManager-aanroep
loaderData.organization.logo als currentPicture in plaats van
organization.metadata.data?.image. Pas ook de rollback-logica van
delete_profile_picture aan zodat deze de raw organization gebruikt en niet
currentOrganization.metadata.data; behoud de bestaande upload-, delete- en
fallbackstatussen.

In `@fdm-app/app/routes/organization.new.tsx`:
- Line 141: Update the uploadObject calls in the organization new flow and
welcome flow to pass the image buffer (organizationImage.buffer and the
corresponding profile-picture buffer) instead of the MD5 hash, while preserving
the existing object key and MIME arguments.

In `@fdm-app/app/routes/user.settings.profile.edit.tsx`:
- Around line 189-207: Update the delete_profile_picture branch in the action
handler to return a response after completing the deletion flow, including the
same success toast/redirect behavior used by the other intent branches. Ensure
both successful deletion and the existing restoration path after deleteObject
failure produce a valid response instead of falling through with undefined.
- Around line 158-182: In the profile-picture update flow, move the
auth.api.updateUser call that sets the new image URL until after uploadObject
succeeds, matching the upload-then-pointer ordering used by the equivalent
welcome flow. Preserve cleanup by deleting the previous object only after the
pointer update succeeds, and ensure failures remove the newly uploaded object
where needed without reverting a pointer that was never updated.

In `@fdm-app/app/routes/welcome.tsx`:
- Around line 327-340: Update the uploadObject call in the profilePicture
handling block to pass profilePicture.buffer as the object body instead of
profilePicture.hash, while preserving the existing object key and detected MIME
type arguments.
- Around line 327-348: Update the profilePicture upload flow in the welcome
route to determine the existing profile-picture object key and delete it when
the new detected extension differs, reusing the cleanup behavior from the
profile edit route. Ensure the old object is removed after the new upload
succeeds and preserve the existing rollback of the newly uploaded object when
the operation fails.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 35e0dfda-0e01-4db2-a96a-e854ed6ec5e3

📥 Commits

Reviewing files that changed from the base of the PR and between d9ab2de and 90f05ff.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • fdm-app/app/components/blocks/mijnpercelen/form-upload.tsx
  • fdm-app/app/components/blocks/organization/form.tsx
  • fdm-app/app/components/blocks/organization/schema.ts
  • fdm-app/app/components/blocks/profile/detect-existing.server.ts
  • fdm-app/app/components/blocks/profile/profile-info-form.tsx
  • fdm-app/app/components/blocks/profile/profile-info-schema.ts
  • fdm-app/app/components/blocks/profile/profile-picture-manager.tsx
  • fdm-app/app/components/blocks/profile/profile-picture-schema.ts
  • fdm-app/app/components/blocks/soil/form-upload.tsx
  • fdm-app/app/components/custom/dropzone.tsx
  • fdm-app/app/components/custom/image-cropper.tsx
  • fdm-app/app/components/ui/slider.tsx
  • fdm-app/app/lib/image-upload.client.ts
  • fdm-app/app/lib/organization-helpers.ts
  • fdm-app/app/routes/api.profile-picture.$type.$file_name.ts
  • fdm-app/app/routes/organization.$slug.settings.tsx
  • fdm-app/app/routes/organization.new.tsx
  • fdm-app/app/routes/user.settings.profile._index.tsx
  • fdm-app/app/routes/user.settings.profile.edit.tsx
  • fdm-app/app/routes/welcome.tsx
  • fdm-app/package.json

Comment thread fdm-app/app/components/blocks/organization/form.tsx
Comment thread fdm-app/app/components/blocks/profile/detect-existing.server.ts
Comment thread fdm-app/app/components/blocks/profile/profile-info-form.tsx Outdated
Comment thread fdm-app/app/components/blocks/profile/profile-info-form.tsx
Comment thread fdm-app/app/components/custom/image-cropper.tsx Outdated
Comment thread fdm-app/app/routes/organization.new.tsx Outdated
Comment thread fdm-app/app/routes/user.settings.profile.edit.tsx Outdated
Comment thread fdm-app/app/routes/user.settings.profile.edit.tsx
Comment thread fdm-app/app/routes/welcome.tsx
Comment thread fdm-app/app/routes/welcome.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/gold-actors-type.md:
- Line 5: Update the changelog sentence in gold-actors-type.md to scope the
social-login deletion claim to user profile pictures only; retain the deletion
claim for organization logos without implying they can originate from social
login.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5443851a-81b3-4a46-a3c6-1101b1dd9083

📥 Commits

Reviewing files that changed from the base of the PR and between 90f05ff and 83bbd53.

📒 Files selected for processing (10)
  • .changeset/gold-actors-type.md
  • fdm-app/app/components/blocks/organization/form.tsx
  • fdm-app/app/components/blocks/profile/detect-existing.server.ts
  • fdm-app/app/components/blocks/profile/profile-info-form.tsx
  • fdm-app/app/components/custom/image-cropper.tsx
  • fdm-app/app/routes/api.profile-picture.$type.$file_name.ts
  • fdm-app/app/routes/organization.$slug.settings.tsx
  • fdm-app/app/routes/organization.new.tsx
  • fdm-app/app/routes/user.settings.profile.edit.tsx
  • fdm-app/app/routes/welcome.tsx
💤 Files with no reviewable changes (1)
  • fdm-app/app/components/blocks/profile/profile-info-form.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • fdm-app/app/components/blocks/profile/detect-existing.server.ts
  • fdm-app/app/routes/api.profile-picture.$type.$file_name.ts
  • fdm-app/app/routes/user.settings.profile.edit.tsx
  • fdm-app/app/routes/organization.new.tsx
  • fdm-app/app/components/blocks/organization/form.tsx
  • fdm-app/app/routes/welcome.tsx
  • fdm-app/app/components/custom/image-cropper.tsx
  • fdm-app/app/routes/organization.$slug.settings.tsx

"@nmi-agro/fdm-app": minor
---

Users can now upload their profile picture and organizations can now upload their logo, which will be stored in the GCS and displayed using the existing user.image and organization.logo database record fields. The profile pictures and logos, including those retrieved previously from social login, can also be deleted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Scope the social-login deletion claim to user profile pictures.

The supplied flow demonstrates removal of a user’s imported social image, but not social-login-derived organization logos. Reword this sentence to avoid implying that organization logos can originate from social login.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.changeset/gold-actors-type.md at line 5, Update the changelog sentence in
gold-actors-type.md to scope the social-login deletion claim to user profile
pictures only; retain the deletion claim for organization logos without implying
they can originate from social login.

@BoraIneviNMI
BoraIneviNMI marked this pull request as ready for review July 20, 2026 11:35
@BoraIneviNMI
BoraIneviNMI requested a review from SvenVw July 20, 2026 11:39

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@fdm-app/app/components/blocks/organization/organization-card.tsx`:
- Around line 28-31: Update the OrganizationAvatar alt text in
fdm-app/app/components/blocks/organization/organization-card.tsx (lines 28-31)
to use organization.name ?? "organisatie"; apply the same fallback in
fdm-app/app/routes/organization.$slug._index.tsx (lines 156-162) using
loaderData.organization.name ?? "organisatie".

In `@fdm-app/app/routes/organization`.$slug._index.tsx:
- Around line 156-162: Update the organization data flow feeding the rightNode
OrganizationAvatar to resolve rawOrganization.logo through the existing
signed-URL helper/API before rendering. Ensure the avatar receives the signed
browser URL rather than the persisted private GCS object key, while preserving
the existing alt text and styling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5096ec43-5f6e-4ef6-87f3-529ba2ba6f4b

📥 Commits

Reviewing files that changed from the base of the PR and between 7b64f7b and 23554bb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (5)
  • fdm-app/app/components/blocks/organization/organization-avatar.tsx
  • fdm-app/app/components/blocks/organization/organization-card.tsx
  • fdm-app/app/routes/organization.$slug._index.tsx
  • fdm-app/app/routes/organization.tsx
  • fdm-app/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
  • fdm-app/package.json

Comment thread fdm-app/app/components/blocks/organization/organization-card.tsx
Comment thread fdm-app/app/routes/organization.$slug._index.tsx
},
})
} catch (err) {
if (uploaded) {
@SvenVw
SvenVw merged commit 0ca6f15 into development Jul 23, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Profile picture upload for users and organizations

2 participants