-
Notifications
You must be signed in to change notification settings - Fork 216
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
base: develop
Are you sure you want to change the base?
Conversation
There was a problem hiding this 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.
return /^[A-Za-z0-9+/=]+$/.test(data); | ||
}; | ||
|
||
const ImageUpload = ({ open, onClose, onUpdate, value, currentImage = value, theme, shouldRender = true, placeholder, maxSize, acceptedTypes, previewSize = 150, onError,}) => { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"; | ||
|
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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"; |
There was a problem hiding this comment.
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({}); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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}> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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."} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
I have implemented most of the requested changes. Let me know if it needs further changes. |
There was a problem hiding this 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.
import CloudUploadIcon from "@mui/icons-material/CloudUpload"; | ||
import { checkImage } from "../../Utils/fileUtils"; | ||
import "./index.css"; | ||
|
There was a problem hiding this comment.
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; | ||
} | ||
|
There was a problem hiding this comment.
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.
I'll look into that |
}; | ||
|
||
// Handles image file selection | ||
const handlePicture = (event) => { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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")} |
There was a problem hiding this comment.
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
onClose={() => setIsOpen("")} | ||
onCancel={() => setIsOpen("")} |
There was a problem hiding this comment.
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} |
There was a problem hiding this comment.
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
There was a problem hiding this 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
@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 |
<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", | ||
}} | ||
/> |
There was a problem hiding this comment.
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 }} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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://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
Not sure about the config prop @vishnusn77 . It will create another lever for the props, but virtually the same props would be there. |
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.