Skip to content
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

Refactor/fe/image upload component #1737

Draft
wants to merge 28 commits into
base: develop
Choose a base branch
from

Conversation

vishnusn77
Copy link
Contributor

@vishnusn77 vishnusn77 commented Feb 10, 2025

Describe your changes

Refactored Image upload component to include all the functionality under single component

Issue number

#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 labelled the PR correctly.
  • 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.
  • 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 February 11, 2025 00:28
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.

A good start! There are many hardcoded values that need to be refactored, and the error handling probably should be moved to the parent component.

I realize many of the hardcoded values are from a previous dev, so if you're not sure how to deal with them please reach out to @marcelluscaio , he knows the most about the theme and how it can be modified to refactor out those hardcoded values.

Client/src/Components/ImageUpload/index.jsx Outdated Show resolved Hide resolved
return /^[A-Za-z0-9+/=]+$/.test(data);
};

const ImageUpload = ({ open, onClose, onUpdate, value, currentImage = value, theme, shouldRender = true, placeholder, maxSize, acceptedTypes, previewSize = 150, onError,}) => {
Copy link
Collaborator

@ajhollid ajhollid Feb 11, 2025

Choose a reason for hiding this comment

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

Theme doesn't need to be a prop, you can use the useTheme hook.

This is also an exceptionally complicated API 😂 Can we look at simplifying this? I don't think we need to take value and currentImage props as that can be maintained internally? The simpler the API the better.

Also feel free to create component variants as well to keep the API simple.

const ImageUpload = () => { ...}
export const ImageUploadRoundPreview = () => {
    // Do stuff needed here
    <ImageUpload round={true}/>
}

export default ImageUpload

Something along those lines. The more complexity we can hide the better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Now using the useTheme hook directly.
Removed unnecessary props.

I'm still Exploring with the component variants. Are there specific use cases you had in mind?

import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import { checkImage } from "../../Utils/fileUtils";
import "./index.css";

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please include JSdoc for the component, see other components for examples

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please leave comments unresolved so other reviewers can see what's been happening and changes made 👍

import {GenericDialog} from "../Dialog/genericDialog";
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import { checkImage } from "../../Utils/fileUtils";
import "./index.css";
Copy link
Collaborator

Choose a reason for hiding this comment

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

We shouldn't be using css for styling, this can be removed


const [file, setFile] = useState();
const [progress, setProgress] = useState({ value: 0, isLoading: false });
const [errors, setErrors] = useState({});
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't think it makes sense to handle errors internally as this component will almost certainly be used in a form which will need errors set on it.

Probably the best thing to do is accept a setErrors prop and allow the caller to handle the errors. Open to discussion on this one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated the component to accept setErrors as a prop.
Errors are no longer handled internally. Let me know if this aligns with your expectation.

<Stack
className="custom-file-text"
alignItems="center"
gap="4px"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use theme.spacing() for gaps

color={theme.palette.primary.contrastTextTertiary}
sx={{ opacity: 0.6 }}
>
(maximum size: 3MB)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This should be configurable by prop

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Resolved

<CloudUploadIcon />
</IconButton>
<Typography component="h2" color={theme.palette.primary.contrastTextTertiary}>
<Typography component="span" fontSize="inherit" color="info" fontWeight={500}>
Copy link
Collaborator

Choose a reason for hiding this comment

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

fontWeight should not be hardcoded, please use the theme. @marcelluscaio is the most knowledgeable on the theme so please reach out

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure we should be using color info, will take a look into that

onClose={onClose}
theme={theme}
title={"Upload Image"}
description={"Select an image to upload."}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Strings should not be hardcoded anymore as we are moving towards providing localization. Please see the il8n.js file for details on the localization system. Example of implementation in the Login page. Documentation here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replaced hardcoded strings with i18n

} else if (typeof currentImage !== "undefined" && isValidBase64Image(currentImage)) {
imageSrc = `data:image/png;base64,${currentImage}`;
} else if (typeof placeholder !== "undefined") {
imageSrc = placeholder;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This value is never used?

@vishnusn77
Copy link
Contributor Author

I have implemented most of the requested changes. Let me know if it needs further changes.
Haven't messed with the hardcoded theme values yet. So that remains. I'll talk with @marcelluscaio and get that fixed as well.

@vishnusn77 vishnusn77 requested a review from ajhollid February 13, 2025 04:49
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.

There's a bug in here somewhere, if I click on "Delete" on the profile page it is removed from the preview, but if I go to "Update" then the photo is still loaded into the image upload component.

Uploading Screencast from 2025-02-13 10-56-57.webm…

import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import { checkImage } from "../../Utils/fileUtils";
import "./index.css";

Copy link
Collaborator

Choose a reason for hiding this comment

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

Please leave comments unresolved so other reviewers can see what's been happening and changes made 👍

if (setErrors) setErrors?.((prev) => ({ ...prev, picture: errorMsg }));
return;
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Cool, I like the early returns.

@vishnusn77
Copy link
Contributor Author

There's a bug in here somewhere, if I click on "Delete" on the profile page it is removed from the preview, but if I go to "Update" then the photo is still loaded into the image upload component.

Uploading Screencast from 2025-02-13 10-56-57.webm…

I'll look into that

};

// Handles image file selection
const handlePicture = (event) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Name can be a bit confusing here. The parameter expects an event, but we are passing an object that contains part of the event, I think we should rename that

return prevErrors;
});
}
return !!error;
Copy link
Contributor

Choose a reason for hiding this comment

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

What are the possibilities for the error returned?

color="accent"
onClick={openPictureModal}
onClick={() => setIsOpen("picture")}
Copy link
Contributor

Choose a reason for hiding this comment

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

I prefer the approach where we pass down an abstraction of the setter function instead of the function itself. That way if implementation changes we dont need to touch the consumer, only the function that is being passed down

Comment on lines +314 to 315
onClose={() => setIsOpen("")}
onCancel={() => setIsOpen("")}
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here, we shoudl avoid passing down the state setter

setErrors={setErrors}
errors={errors}
open={isOpen === "picture"}
onClose={setIsOpen}
Copy link
Contributor

Choose a reason for hiding this comment

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

I would pass down a function that closes the modal, instead of passing the setter

Copy link
Contributor

@marcelluscaio marcelluscaio left a comment

Choose a reason for hiding this comment

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

It looks good in general, I made some suggestions, let me know you opinion on them

@ajhollid
Copy link
Collaborator

ajhollid commented Feb 14, 2025

@marcelluscaio @vishnusn77 any ideas on simplifying this components API? The long list of props is making me nervous 😂

I realize this one is tricky as we're trying to refactor an existing component.

In your opinion would it make more sense to start from scratch and design a more reasonable API and then make the rest of the app conform to it? Or should we continue on and make the component conform to the application instead?

My instinct is to design an ideal component and the rest of the app to work with it 🤔

@vishnusn77
Copy link
Contributor Author

@marcelluscaio @vishnusn77 any ideas on simplifying this components API? The long list of props is making me nervous 😂

I realize this one is tricky as we're trying to refactor an existing component.

In your opinion would it make more sense to start from scratch and design a more reasonable API and then make the rest of the app conform to it? Or should we continue on and make the component conform to the application instead?

My instinct is to design an ideal component and the rest of the app to work with it 🤔

How about we wrap all the optional parameters into a single prop? Instead of passing multiple individual props like placeholder, maxSize, and acceptedTypes, we can do something like:

<ImageUpload
open
onClose
onUpdate
setErrors
config={{ placeholder, maxSize, acceptedTypes }}
/>

Comment on lines +190 to +205
<input
id="update-profile-picture"
type="file"
onChange={handlePicture}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: "100%",
opacity: 0,
cursor: "pointer",
zIndex: 3,
pointerEvents: "all",
}}
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it possible to use MUIs Input component?

<Typography
component="p"
color={theme.palette.primary.contrastTextTertiary}
sx={{ opacity: 0.6 }}
Copy link
Contributor

Choose a reason for hiding this comment

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

Have we tested if this passes contrast tests?

Copy link
Contributor

@gorkem-bwl gorkem-bwl Feb 17, 2025

Choose a reason for hiding this comment

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

I think we can guide devs on how to do contrast tests - maybe a link or similar?

Copy link
Contributor

Choose a reason for hiding this comment

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

Absolutely!
Normally I use these two tools:

https://colourcontrast.cc/

https://webaim.org/resources/contrastchecker/

However the first one doesn't support opacity changes. So basically you put the hex values and change the alpha to 0.6, which is the opacity being used here

@marcelluscaio
Copy link
Contributor

Not sure about the config prop @vishnusn77 . It will create another lever for the props, but virtually the same props would be there.
I wonder if we need open and close on the parent level. What if button and modal were in the same component?

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.

4 participants