Added input ref to track file
This commit is contained in:
1 parent
5807990342
commit
d7c1972c99
3 files changed
+127
-122
No files matched your search
@@ -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,
|
||||
};
|
||||
|
||||
|
||||
Reference in new issue
Block a user