Added input ref to track file

This commit is contained in:
Daniel Cojocea committed 2024-07-05 14:43:29 -04:00
1 parent 5807990342
commit d7c1972c99
3 files changed
+127 -122

No files matched your search

+9 -1
View File
@@ -40,7 +40,15 @@ const Avatar = ({ src, small, sx }) => {
return (
<MuiAvatar
alt={`${user?.firstname} ${user?.lastname}`}
src={src ? src : user?.profileImage ? image : ""}
src={
src === null
? "/static/images/avatar/2.jpg"
: src
? src
: user?.profileImage
? image
: "/static/images/avatar/2.jpg"
}
sx={{
fontSize: small ? "12px" : "20px",
display: "inline-flex",
@@ -1,5 +1,5 @@
import { useTheme } from "@emotion/react";
import { useEffect, useRef, useState } from "react";
import { useRef, useState } from "react";
import TabPanel from "@mui/lab/TabPanel";
import { Box, Divider, Modal, Stack, Typography } from "@mui/material";
import ButtonSpinner from "../../ButtonSpinner";
@@ -68,10 +68,10 @@ const ProfilePanel = () => {
validateField({ [name]: value }, editProfileValidation, name);
};
//holds file info
const [file, setFile] = useState({});
const [progress, setProgress] = useState({ value: 0, isLoading: false });
const fileRef = useRef();
const intervalRef = useRef(null);
const [src, setSrc] = useState();
const [progress, setProgress] = useState({ value: 0, isLoading: false });
const handlePicture = (event) => {
const pic = event.target.files[0];
let error = validateField(
@@ -80,33 +80,20 @@ const ProfilePanel = () => {
);
if (error) return;
if (pic) {
setProgress((prev) => ({ ...prev, isLoading: true }));
setFile({ pic: pic, name: pic.name, size: formatBytes(pic.size) });
setProgress((prev) => ({ ...prev, isLoading: true }));
setSrc(URL.createObjectURL(fileRef.current.files[0]));
//read image
const reader = new FileReader();
reader.onload = () => {
const base64 = reader.result;
setFile((prev) => ({
...prev,
src: base64,
}));
//TODO - potentitally remove once image compression functions are implemented above
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);
};
reader.readAsDataURL(pic);
}
//TODO - potentitally remove once image compression functions are implemented above
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);
};
const validateField = (toValidate, schema, name = "picture") => {
@@ -122,7 +109,11 @@ const ProfilePanel = () => {
const removePicture = () => {
clearError("picture");
setFile({});
if (fileRef.current && fileRef.current.files[0]) {
fileRef.current.value = "";
URL.revokeObjectURL(fileRef.current?.files[0]);
setSrc();
}
//interrupt interval if image upload is canceled prior to completing the process
clearInterval(intervalRef.current);
setProgress({ value: 0, isLoading: false });
@@ -131,21 +122,23 @@ const ProfilePanel = () => {
removePicture();
setIsOpen("");
};
const handleUpdatePicture = () => {
setProgress({ value: 0, isLoading: false });
setLocalData((prev) => ({
...prev,
profileImage: file.pic,
profileImage: fileRef.current?.files[0],
}));
setFile({});
setIsOpen("");
};
const handleDeletePicture = () => {
setLocalData((prev) => ({
...prev,
profileImage: "",
profileImage: null,
}));
setSrc(null);
};
//TODO - implement delete account function
const handleDeleteAccount = () => {};
const handleSaveProfile = (event) => {
@@ -247,12 +240,7 @@ const ProfilePanel = () => {
</Typography>
</Stack>
<Stack className="row-stack" direction="row" alignItems="center">
<Avatar
src={file?.src ? file.src : ""}
sx={{
mr: "8px",
}}
/>
<Avatar src={src} sx={{ mr: "8px" }} />
<Button
level="tertiary"
label="Delete"
@@ -388,15 +376,16 @@ const ProfilePanel = () => {
</Typography>
<ImageField
id="update-profile-picture"
picture={file?.src ? file?.src : ""}
src={src}
loading={progress.isLoading && progress.value !== 100}
onChange={handlePicture}
ref={fileRef}
/>
{progress.isLoading || progress.value !== 0 || errors["picture"] ? (
<ProgressUpload
icon={<ImageIcon />}
label={file.name}
size={file.size}
label={fileRef.current?.files[0]?.name}
size={formatBytes(fileRef.current?.files[0]?.size)}
progress={progress.value}
onClick={removePicture}
error={errors["picture"]}
@@ -1,4 +1,4 @@
import React, { useState } from "react";
import React, { useState, forwardRef } from "react";
import PropTypes from "prop-types";
import { Box, IconButton, Stack, TextField, Typography } from "@mui/material";
import { useTheme } from "@emotion/react";
@@ -9,12 +9,12 @@ import { checkImage } from "../../../Utils/fileUtils";
/**
* @param {Object} props - The component props.
* @param {string} props.id - The unique identifier for the input field.
* @param {string} props.picture - The URL of the image to display.
* @param {string} props.src - The URL of the image to display.
* @param {function} props.onChange - The function to handle file input change.
* @returns {JSX.Element} The rendered component.
*/
const ImageField = ({ id, picture, loading, onChange }) => {
const ImageField = forwardRef(({ id, src, loading, onChange }, ref) => {
const theme = useTheme();
const [isDragging, setIsDragging] = useState(false);
@@ -26,81 +26,87 @@ const ImageField = ({ id, picture, loading, onChange }) => {
};
return (
<>
{!checkImage(picture) || loading ? (
<>
<Box
className="image-field-wrapper"
mt="20px"
<Box>
<Box
className="image-field-wrapper"
mt="20px"
sx={{
display: !checkImage(src) || loading ? "block" : "none",
position: "relative",
height: "fit-content",
border: "dashed",
borderRadius: `${theme.shape.borderRadius}px`,
borderColor: isDragging
? theme.palette.primary.main
: theme.palette.otherColors.graishWhite,
borderWidth: "2px",
transition: "0.2s",
"&:hover": {
borderColor: theme.palette.primary.main,
backgroundColor: "hsl(215, 87%, 51%, 0.05)",
},
}}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDrop={handleDragLeave}
>
<TextField
id={id}
type="file"
onChange={onChange}
inputRef={ref}
sx={{
width: "100%",
"& .MuiInputBase-input[type='file']": {
opacity: 0,
cursor: "pointer",
maxWidth: "500px",
minHeight: "175px",
},
"& fieldset": {
padding: 0,
border: "none",
},
}}
/>
<Stack
className="custom-file-text"
alignItems="center"
gap="4px"
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
zIndex: "-1",
width: "100%",
}}
>
<IconButton
sx={{
position: "relative",
height: "fit-content",
border: "dashed",
pointerEvents: "none",
borderRadius: `${theme.shape.borderRadius}px`,
borderColor: isDragging
? theme.palette.primary.main
: theme.palette.otherColors.graishWhite,
borderWidth: "2px",
transition: "0.2s",
"&:hover": {
borderColor: theme.palette.primary.main,
backgroundColor: "hsl(215, 87%, 51%, 0.05)",
},
border: `solid ${theme.shape.borderThick}px ${theme.palette.otherColors.graishWhite}`,
boxShadow: theme.shape.boxShadow,
}}
onDragEnter={handleDragEnter}
onDragLeave={handleDragLeave}
onDrop={handleDragLeave}
>
<TextField
id={id}
type="file"
onChange={onChange}
sx={{
width: "100%",
"& .MuiInputBase-input[type='file']": {
opacity: 0,
cursor: "pointer",
maxWidth: "500px",
minHeight: "175px",
},
"& fieldset": {
padding: 0,
border: "none",
},
}}
/>
<Stack
className="custom-file-text"
alignItems="center"
gap="4px"
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
zIndex: "-1",
width: "100%",
}}
>
<IconButton
sx={{
pointerEvents: "none",
borderRadius: `${theme.shape.borderRadius}px`,
border: `solid ${theme.shape.borderThick}px ${theme.palette.otherColors.graishWhite}`,
boxShadow: theme.shape.boxShadow,
}}
>
<CloudUploadIcon />
</IconButton>
<Typography component="h2">
<span>Click to upload</span> or drag and drop
</Typography>
<Typography component="p">(maximum size: 3MB)</Typography>
</Stack>
</Box>
<Typography component="p">Supported formats: JPG, PNG</Typography>
</>
) : (
<CloudUploadIcon />
</IconButton>
<Typography component="h2">
<span>Click to upload</span> or drag and drop
</Typography>
<Typography component="p">(maximum size: 3MB)</Typography>
</Stack>
</Box>
<Typography
component="p"
sx={{
display: !checkImage(src) || loading ? "block" : "none",
}}
>
Supported formats: JPG, PNG
</Typography>
{checkImage(src) && !loading ? (
<Stack direction="row" justifyContent="center">
<Box
sx={{
@@ -108,19 +114,21 @@ const ImageField = ({ id, picture, loading, onChange }) => {
height: "250px",
borderRadius: "50%",
overflow: "hidden",
backgroundImage: `url(${picture})`,
backgroundImage: `url(${src})`,
backgroundSize: "cover",
}}
></Box>
</Stack>
) : (
""
)}
</>
</Box>
);
};
});
ImageField.propTypes = {
id: PropTypes.string.isRequired,
picture: PropTypes.string,
src: PropTypes.string,
onChange: PropTypes.func.isRequired,
};