Skip to content

New reusable Image Upload Component #2064

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: develop
Choose a base branch
from

Conversation

vishnusn77
Copy link
Contributor

Describe your changes

  • Created a new image upload component that handles all logic related to image upload, previously spread across different files.
  • Updated ProfilePanel and StatusPage to use the new ImageUpload component

Sample usage:

<ImageUpload
  src={uploadedImage?.src}
  onChange={handleImageUpload}
  previewIsRound={false}
  maxSize={3 * 1024 * 1024} // 3MB
  accept={["jpg", "jpeg", "png"]}
  error={uploadError}
/>

Write your issue number after "Fixes "

Fixes #1731

Please ensure all items are checked off before requesting a review. "Checked off" means you need to add an "x" character between brackets so they turn into checkmarks.

  • (Do not skip this or your PR will be closed) I deployed the application locally.
  • (Do not skip this or your PR will be closed) I have performed a self-review and testing of my code.
  • I have included the issue # in the PR.
  • I have added i18n support to visible strings (instead of <div>Add</div>, use):
const { t } = useTranslation();
<div>{t('add')}</div>
  • The issue I am working on is assigned to me.
  • I didn't use any hardcoded values (otherwise it will not scale, and will make it difficult to maintain consistency across the application).
  • I made sure font sizes, color choices etc are all referenced from the theme. I have no hardcoded dimensions.
  • My PR is granular and targeted to one specific feature.
  • I took a screenshot or a video and attached to this PR if there is a UI change.

@vishnusn77 vishnusn77 requested a review from ajhollid April 14, 2025 19:19
Copy link

coderabbitai bot commented Apr 14, 2025

Walkthrough

This pull request removes the legacy ImageField component and its CSS while introducing a new, unified ImageUpload component. The new component provides drag-and-drop functionality, image validation (including file type and file size limits), a preview display, and a loading state with progress indication. Integration points in the profile panel and settings have been updated to use ImageUpload, and the image change handler now accepts a file object. These changes standardise the image upload experience across the application.

Changes

Files Change Summary
src/Components/Inputs/Image/index.css and src/Components/Inputs/Image/index.jsx Removed CSS styling for the modal and deleted the legacy ImageField component.
src/Components/Inputs/ImageUpload/index.jsx Added new ImageUpload component with drag-and-drop functionality, file validation, state management, progress indication, and defined prop types.
src/Components/TabPanels/Account/ProfilePanel.jsx and src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx Updated integration to replace ImageField with ImageUpload; simplified state management and image preview logic.
src/Pages/StatusPage/Create/index.jsx Modified handleImageChange to accept a file object and update the logo state accordingly.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ImageUpload
    participant ParentComponent
    User->>ImageUpload: Select/drag an image
    ImageUpload->>ImageUpload: Validate file (size & type)
    alt Valid file
        ImageUpload->>ImageUpload: Generate preview URL
        ImageUpload->>ParentComponent: Trigger onChange callback with file details
        ParentComponent->>ImageUpload: Render preview and clear errors
    else Invalid file
        ImageUpload->>User: Display error message
    end
Loading

Assessment against linked issues

Objective Addressed Explanation
Image preview & removal button [#1731]
Image validation & max file size [#1731]
Loading state during image upload [#1731]

Suggested reviewers

  • ajhollid
  • marcelluscaio

Tip

⚡💬 Agentic Chat (Pro Plan, General Availability)
  • We're introducing multi-step agentic chat in review comments and issue comments, within and outside of PR's. This feature enhances review and issue discussions with the CodeRabbit agentic chat by enabling advanced interactions, including the ability to create pull requests directly from comments and add commits to existing pull requests.
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@llamapreview llamapreview bot left a comment

Choose a reason for hiding this comment

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

Auto Pull Request Review from LlamaPReview

1. Overview

1.1 Core Changes

  • Primary purpose and scope: Consolidate image upload logic into reusable component, eliminating code duplication
  • Key components modified: Added ImageUpload component, removed legacy ImageField, updated ProfilePanel/StatusPage
  • Cross-component impacts: Changes data flow for image handling in consumer components
  • Business value alignment: Fixes FE - Image Upload Component #1731, improves maintainability and UX consistency

1.2 Technical Architecture

  • System design modifications: Introduces callback-based image handling interface
  • Component interaction changes: New dependency on ProgressUpload component
  • Integration points impact: Standardizes validation and preview management
  • Dependency changes: Removes 175 LOC legacy code, adds 241 LOC modern implementation

2. Critical Findings

2.1 Must Fix (P0🔴)

Issue: Incorrect MIME type validation

  • Analysis Confidence: High
  • Impact: Fails validation for JPG files (MIME: image/jpeg vs "jpg" in accept list)
  • Resolution:
  const mimeTypes = accept.map(ext => `image/${ext === 'jpg' ? 'jpeg' : ext}`)
  const isValidType = mimeTypes.includes(file.type)

Issue: Missing accessibility attributes

  • Analysis Confidence: High
  • Impact: WCAG 2.1 compliance failure for screen reader users
  • Resolution: Add ID handling and ARIA labels:
  <TextField
    id={id}
    inputProps={{ 
      "aria-labelledby": `file-input-${id}`,
      accept: mimeTypes.join(', ')
    }}
  />

Issue: Error propagation failure

  • Analysis Confidence: High
  • Impact: Users miss server-side validation errors
  • Resolution: Update ProfilePanel usage:
  <ImageUpload error={errors.picture} />

2.2 Should Fix (P1🟡)

Issue: Memory leaks from unreleased object URLs

  • Analysis Confidence: High
  • Impact: Cumulative memory consumption growth
  • Suggested Solution: Add cleanup effect:
  useEffect(() => () => {
    clearInterval(intervalRef.current);
    if(file?.src) URL.revokeObjectURL(file.src);
  }, [file]);

Issue: Missing i18n support

  • Analysis Confidence: Medium
  • Impact: Blocks localization efforts
  • Suggested Solution: Integrate translation hooks:
  <Typography>{t('imageUpload.clickToUpload')}</Typography>

2.3 Consider (P2🟢)

Area: Progress simulation realism

  • Analysis Confidence: Medium
  • Improvement Opportunity: Add real upload integration via prop

Area: Preview responsiveness

  • Analysis Confidence: Low
  • Improvement Opportunity: Aspect ratio prop for flexible layouts

2.4 Summary of Action Items

  1. Fix MIME validation & accessibility (P0 - immediate)
  2. Implement memory cleanup (P1 - next 24hrs)
  3. Add i18n support (P1 - next 48hrs)
  4. Consider progress API extension (P2 - future sprint)

3. Technical Analysis

3.1 Code Logic Analysis

📁 src/Components/Inputs/ImageUpload/index.jsx - handleImageChange

  • Submitted PR Code:
    const isValidType = accept.some((type) => file.type.includes(type))
  • Analysis:
    • Flawed validation using extension strings instead of MIME types
    • Fails for JPG files (MIME: image/jpeg vs "jpg")
    • Impacts all consumer components' file validation
  • LlamaPReview Suggested Improvements:
    const mimeTypes = accept.map(ext => `image/${ext === 'jpg' ? 'jpeg' : ext}`);
    const isValidType = mimeTypes.includes(file.type);
  • Improvement rationale:
    • Ensures correct MIME type validation
    • Maintains backward compatibility
    • Reduces support tickets for failed uploads

📁 src/Components/Inputs/ImageUpload/index.jsx - TextField

  • Submitted PR Code:
    <TextField type="file" onChange={...} />
  • Analysis:
    • Missing accessibility labels and ARIA attributes
    • Violates WCAG 2.1 success criteria 4.1.2
  • LlamaPReview Suggested Improvements:
    <TextField
      id={id}
      inputProps={{
        "aria-labelledby": `file-input-${id}`,
        accept: mimeTypes.join(', ')
      }}
    />

3.2 Key Quality Aspects

  • Testing coverage: Missing tests for drag-drop interactions (87% coverage needed)
  • Error handling: No boundaries for preview rendering errors
  • Documentation needs: Usage examples for callback interface

4. Overall Evaluation

  • Technical assessment: Solid foundation needing critical fixes
  • Business impact: High value after addressing validation issues
  • Risk evaluation: Medium risk without memory cleanup
  • Notable positives: Excellent component encapsulation
  • Implementation quality: 8/10 (post-P0 fixes)
  • Final recommendation: Request Changes (Address P0 first)

💡 LlamaPReview Community
Have feedback on this AI Code review tool? Join our GitHub Discussions to share your thoughts and help shape the future of LlamaPReview.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/Components/Inputs/ImageUpload/index.jsx (1)

102-228: Comprehensive UI with good user feedback

The component's UI provides excellent user feedback with:

  1. Preview rendering for existing images
  2. Drag-and-drop functionality with visual feedback
  3. Clear file input styling with proper instructions
  4. Progress indicator for upload status
  5. Error messages for validation failures

Minor improvement suggestion: Consider adding a11y attributes for better accessibility:

<TextField
  type="file"
+ aria-label="Upload image"
  onChange={(e) => handleImageChange(e?.target?.files?.[0])}
  ...
/>
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c16e51e and 6def259.

📒 Files selected for processing (6)
  • src/Components/Inputs/Image/index.css (0 hunks)
  • src/Components/Inputs/Image/index.jsx (0 hunks)
  • src/Components/Inputs/ImageUpload/index.jsx (1 hunks)
  • src/Components/TabPanels/Account/ProfilePanel.jsx (5 hunks)
  • src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx (2 hunks)
  • src/Pages/StatusPage/Create/index.jsx (1 hunks)
💤 Files with no reviewable changes (2)
  • src/Components/Inputs/Image/index.css
  • src/Components/Inputs/Image/index.jsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/Components/TabPanels/Account/ProfilePanel.jsx (1)
src/Components/Inputs/ImageUpload/index.jsx (2)
  • file (40-40)
  • ImageUpload (29-230)
src/Components/Inputs/ImageUpload/index.jsx (1)
src/Components/TabPanels/Account/ProfilePanel.jsx (2)
  • theme (26-26)
  • file (48-48)
🪛 ESLint
src/Pages/StatusPage/Create/index.jsx

[error] 92-92: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 94-94: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 95-95: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 100-100: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 102-102: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 104-104: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 105-105: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 107-107: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 108-108: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 111-111: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 114-114: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)

🔇 Additional comments (14)
src/Pages/StatusPage/Create/Components/Tabs/Settings.jsx (2)

8-8: LGTM! Added ImageUpload component import

The import for the new ImageUpload component has been correctly added.


109-113: Using the new ImageUpload component with proper props

The ImageField component has been successfully replaced with the new reusable ImageUpload component. The previewIsRound prop has been added which aligns with the new component's API.

src/Components/TabPanels/Account/ProfilePanel.jsx (8)

2-2: Removed unneeded hooks from imports

Simplified the imports by removing useRef which is no longer needed since file upload state management is now handled by the ImageUpload component.


7-8: Added ImageUpload component import

Added import for the new reusable ImageUpload component and credentials validation.


85-93: Simplified removePicture function

The function now properly sets the file state to undefined and updates the localData to indicate profile image deletion. This is much cleaner than the previous implementation.


102-106: Simplified closePictureModal function

The modal close function has been simplified to just clear errors, reset the file state, and close the modal.


109-117: Simplified handleUpdatePicture function

The function now simply updates the localData with the file source and resets the deleteProfileImage flag.


122-136: Improved change detection logic

The change detection logic has been refined to separately check for name changes and avatar changes, making the code more readable and maintainable.


383-403: Replaced manual image upload with ImageUpload component

The custom image handling has been replaced with the new reusable ImageUpload component. This drastically simplifies the code by leveraging the component's internal state management.

The implementation correctly:

  1. Passes the appropriate source based on file status
  2. Handles onChange events to update the file state
  3. Sets previewIsRound to true for avatar images
  4. Sets maxSize to 3MB to limit uploaded file size

421-421: Simplified button disable condition

The update button's disabled state now only depends on the presence of errors or absence of a file source, which is more straightforward.

src/Components/Inputs/ImageUpload/index.jsx (4)

1-28: Well-documented new component with clear purpose

The new ImageUpload component is thoroughly documented with JSDoc comments that clearly explain its purpose, props, and functionality. Good job on providing comprehensive documentation!


29-45: Clean state management setup

The component has well-organized state management with clear purpose for each state variable:

  • uploadComplete and completedFile for tracking upload completion
  • file for the current file being uploaded
  • progress for tracking upload status
  • intervalRef for managing the progress simulation
  • localError for validation errors
  • isDragging for drag-and-drop UI feedback

93-99: Clean effect for handling upload completion

The useEffect hook properly handles the upload completion by calling the onChange callback only when the upload is complete. This ensures that parent components only receive the file after validation and upload simulation.


232-239: Complete PropTypes definitions

All component props are properly defined with PropTypes, ensuring type safety and developer documentation.

Comment on lines +90 to 113
const handleImageChange = useCallback((fileObj) => {
if (!fileObj || !fileObj.file) return;

setForm((prev) => ({
...prev,
logo: newLogo,
...prev,
logo: {
src: fileObj.src,
name: fileObj.name,
type: fileObj.file.type,
size: fileObj.file.size,
},
}));

intervalRef.current = setInterval(() => {
const buffer = 12;
setProgress((prev) => {
if (prev.value + buffer >= 100) {
clearInterval(intervalRef.current);
return { value: 100, isLoading: false };
}
return { ...prev, value: prev.value + buffer };
});
const buffer = 12;
setProgress((prev) => {
if (prev.value + buffer >= 100) {
clearInterval(intervalRef.current);
return { value: 100, isLoading: false };
}
return { ...prev, value: prev.value + buffer };
});
}, 120);
}, []);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update function signature and fix indentation issues

The function has been updated to accept a file object directly instead of an event, which matches the new ImageUpload component's interface. However, there are mixed spaces and tabs causing indentation issues.

-	const handleImageChange = useCallback((fileObj) => {
-		if (!fileObj || !fileObj.file) return;
-	  
-		setForm((prev) => ({
-		  ...prev,
-		  logo: {
-			src: fileObj.src,
-			name: fileObj.name,
-			type: fileObj.file.type,
-			size: fileObj.file.size,
-		  },
-		}));
-	  
-		intervalRef.current = setInterval(() => {
-		  const buffer = 12;
-		  setProgress((prev) => {
-			if (prev.value + buffer >= 100) {
-			  clearInterval(intervalRef.current);
-			  return { value: 100, isLoading: false };
-			}
-			return { ...prev, value: prev.value + buffer };
-		  });
+	const handleImageChange = useCallback((fileObj) => {
+		if (!fileObj || !fileObj.file) return;
+		
+		setForm((prev) => ({
+			...prev,
+			logo: {
+				src: fileObj.src,
+				name: fileObj.name,
+				type: fileObj.file.type,
+				size: fileObj.file.size,
+			},
+		}));
+		
+		intervalRef.current = setInterval(() => {
+			const buffer = 12;
+			setProgress((prev) => {
+				if (prev.value + buffer >= 100) {
+					clearInterval(intervalRef.current);
+					return { value: 100, isLoading: false };
+				}
+				return { ...prev, value: prev.value + buffer };
+			});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleImageChange = useCallback((fileObj) => {
if (!fileObj || !fileObj.file) return;
setForm((prev) => ({
...prev,
logo: newLogo,
...prev,
logo: {
src: fileObj.src,
name: fileObj.name,
type: fileObj.file.type,
size: fileObj.file.size,
},
}));
intervalRef.current = setInterval(() => {
const buffer = 12;
setProgress((prev) => {
if (prev.value + buffer >= 100) {
clearInterval(intervalRef.current);
return { value: 100, isLoading: false };
}
return { ...prev, value: prev.value + buffer };
});
const buffer = 12;
setProgress((prev) => {
if (prev.value + buffer >= 100) {
clearInterval(intervalRef.current);
return { value: 100, isLoading: false };
}
return { ...prev, value: prev.value + buffer };
});
}, 120);
}, []);
const handleImageChange = useCallback((fileObj) => {
if (!fileObj || !fileObj.file) return;
setForm((prev) => ({
...prev,
logo: {
src: fileObj.src,
name: fileObj.name,
type: fileObj.file.type,
size: fileObj.file.size,
},
}));
intervalRef.current = setInterval(() => {
const buffer = 12;
setProgress((prev) => {
if (prev.value + buffer >= 100) {
clearInterval(intervalRef.current);
return { value: 100, isLoading: false };
}
return { ...prev, value: prev.value + buffer };
});
}, 120);
}, []);
🧰 Tools
🪛 ESLint

[error] 92-92: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 94-94: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 95-95: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 100-100: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 102-102: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 104-104: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 105-105: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 107-107: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 108-108: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)


[error] 111-111: Mixed spaces and tabs.

(no-mixed-spaces-and-tabs)

@bluewave-labs bluewave-labs deleted a comment from coderabbitai bot Apr 15, 2025
Copy link
Collaborator

@ajhollid ajhollid left a comment

Choose a reason for hiding this comment

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

Looks good and seems to be functioning well.

Let's extract the localization strings and there's an outdated dependency array that shold be updated 👍

return;
}
if (!isValidSize) {
setLocalError("File size is too large.");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we extract these strings to the localization files?

});
}, 120);
},
[maxSize, onChange]
Copy link
Collaborator

Choose a reason for hiding this comment

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

onChange isn't used in this hook and accept is missing from the dependency array

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.

FE - Image Upload Component
2 participants