From c1d0c24f785a3506339aad3b0868b90ff4db9214 Mon Sep 17 00:00:00 2001 From: Daniel Cojocea Date: Wed, 3 Jul 2024 13:04:46 -0400 Subject: [PATCH 1/9] Refactored code and hooked up picture to localState --- Client/src/Components/ProgressBars/index.css | 3 +- Client/src/Components/ProgressBars/index.jsx | 3 + .../TabPanels/Account/ProfilePanel.jsx | 200 ++++++++++-------- .../src/Components/TextFields/Image/index.jsx | 5 +- Client/src/Utils/fileUtils.js | 21 ++ 5 files changed, 135 insertions(+), 97 deletions(-) create mode 100644 Client/src/Utils/fileUtils.js diff --git a/Client/src/Components/ProgressBars/index.css b/Client/src/Components/ProgressBars/index.css index 8bd3fc966..5ba1939da 100644 --- a/Client/src/Components/ProgressBars/index.css +++ b/Client/src/Components/ProgressBars/index.css @@ -17,7 +17,8 @@ padding-left: 2px; } .progress-bar-container:has(p.input-error){ - border-color: var(--env-var-color-24); + border-color: var(--env-var-color-19); + background-color: var(--env-var-color-21); padding: 8px var(--env-var-spacing-1-plus); } .progress-bar-container p.input-error{ diff --git a/Client/src/Components/ProgressBars/index.jsx b/Client/src/Components/ProgressBars/index.jsx index 2d60683ef..40e1a5a2c 100644 --- a/Client/src/Components/ProgressBars/index.jsx +++ b/Client/src/Components/ProgressBars/index.jsx @@ -94,6 +94,9 @@ const ProgressUpload = ({ } : { ml: "auto", + "&:focus": { + outline: "none", + }, } } > diff --git a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx b/Client/src/Components/TabPanels/Account/ProfilePanel.jsx index 2208fd646..a9d5a5c81 100644 --- a/Client/src/Components/TabPanels/Account/ProfilePanel.jsx +++ b/Client/src/Components/TabPanels/Account/ProfilePanel.jsx @@ -1,5 +1,5 @@ import { useTheme } from "@emotion/react"; -import { 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"; @@ -16,6 +16,7 @@ import { update } from "../../../Features/Auth/authSlice"; import ImageField from "../../TextFields/Image"; import ImageIcon from "@mui/icons-material/Image"; import ProgressUpload from "../../ProgressBars"; +import { bytesToUrl, checkImage, formatBytes } from "../../../Utils/fileUtils"; /** * ProfilePanel component displays a form for editing user profile information @@ -42,12 +43,18 @@ const ProfilePanel = () => { //Disabled for now, will revisit in the future // email: user.email, //TODO - upload picture - profilePicUrl: "placeholder", + profilePicUrl: { src: user.profilePicUrl }, }); const [errors, setErrors] = useState({}); + const clearError = (err) => { + setErrors((prev) => { + const updatedErrors = { ...prev }; + if (updatedErrors[err]) delete updatedErrors[err]; + return updatedErrors; + }); + }; const [isOpen, setIsOpen] = useState(""); const isModalOpen = (name) => isOpen === name; - const [loading, setLoading] = useState(false); const handleChange = (event) => { const { value, id } = event.target; @@ -73,90 +80,84 @@ const ProfilePanel = () => { return updatedErrors; }); }; - - //TODO - add setPicture state to localData - const [picture, setPicture] = useState(); - const [uploadProgress, setUploadProgress] = useState(0); - const [isUploading, setIsUploading] = useState(false); + const [progress, setProgress] = useState({ value: 0, isLoading: false }); + const intervalRef = useRef(null); const handlePicture = (event) => { - clearError("picture"); const pic = event.target.files[0]; - console.log(pic); - //TODO - add setPicture state to localData - setPicture({ name: pic.name, type: pic.type, size: formatBytes(pic.size) }); - setIsUploading(true); + //reset errors if any + clearError("picture"); + if (pic) { + setProgress((prev) => ({ ...prev, isLoading: true })); + setLocalData((prev) => ({ + ...prev, + profilePicUrl: { + name: pic.name, + type: pic.type, + size: formatBytes(pic.size), + src: "", + }, + })); - const { error } = imageValidation.validate( - { - type: pic.type, - size: pic.size, - }, - { abortEarly: false } - ); + const { error } = imageValidation.validate( + { + type: pic.type, + size: pic.size, + }, + { abortEarly: false } + ); - if (error) { - setErrors((prev) => { - const updatedErrors = { ...prev }; - updatedErrors["picture"] = error.details[0].message; - return updatedErrors; - }); - return; - } - - setTimeout(() => { - //TODO - add setPicture state to localData - setPicture((prev) => ({ ...prev, src: URL.createObjectURL(pic) })); - }, 1500); - - let progress = 0; - const interval = setInterval(() => { - progress += 10; - setUploadProgress(progress); - if (progress >= 100) { - clearInterval(interval); + if (error) { + setErrors((prev) => ({ + ...prev, + ["picture"]: error.details[0].message, + })); + return; } - }, 150); - }; - const formatBytes = (bytes) => { - if (bytes === 0) return "0 Bytes"; - const megabytes = bytes / (1024 * 1024); - return megabytes.toFixed(2) + " MB"; - }; - const clearError = (err) => { - setErrors((prev) => { - const updatedErrors = { ...prev }; - if (updatedErrors[err]) delete updatedErrors[err]; - return updatedErrors; - }); - }; - const handleCancelUpload = () => { - //TODO - add setPicture state to localData - setPicture(); - setUploadProgress(0); - setIsUploading(false); - clearError("picture"); - }; - const handleClosePictureModal = () => { - setIsOpen(""); - setUploadProgress(0); - setIsUploading(false); - clearError("picture"); - }; - //TODO - revisit once localData is set up properly - const handleDeletePicture = () => { - setLoading(true); - setTimeout(() => { - //TODO - add setPicture state to localData - setPicture(null); - setLoading(false); - }, 2000); + const reader = new FileReader(); + reader.onloadend = () => { + const arrayBuffer = reader.result; + const bytes = new Uint8Array(arrayBuffer); + setLocalData((prev) => ({ + ...prev, + profilePicUrl: { + ...prev.profilePicUrl, + src: bytes, + }, + })); + + //TODO - potentitally remove once image compression functions are implemented above + intervalRef.current = setInterval(() => { + const buffer = 8; + setProgress((prev) => { + if (prev.value + buffer >= 100) { + clearInterval(intervalRef.current); + return { value: 100, isLoading: false }; + } + return { ...prev, value: prev.value + buffer }; + }); + }, 150); + }; + reader.readAsArrayBuffer(pic); + } }; - //TODO - implement update profile function - const handleUpdatePicture = () => { + const removePicture = () => { + clearError("picture"); + setLocalData((prev) => ({ + ...prev, + profilePicUrl: { src: user.profilePicUrl }, + })); + //interrupt interval if modal closes prior to completing the process + clearInterval(intervalRef.current); + setProgress({ value: 0, isLoading: false }); + }; + const closePictureModal = () => { + removePicture(); + setIsOpen(""); + }; + const handleUpdatePicture = () => { + setProgress({ value: 0, isLoading: false }); setIsOpen(""); - setUploadProgress(0); - setIsUploading(false); }; //TODO - implement delete account function const handleDeleteAccount = () => {}; @@ -261,7 +262,11 @@ const ProfilePanel = () => { { mr: "8px", }} /> -