install file picker and fix UI bugs

This commit is contained in:
luisa.bellitto 2025-06-21 20:19:58 +02:00 committed by Rudi Regentonne
parent df9b44d061
commit 3a0a3e4331
6 changed files with 121 additions and 64 deletions

View file

@ -21,6 +21,7 @@
"react-router-dom": "^7.6.2", "react-router-dom": "^7.6.2",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"typescript": "^4.4.2", "typescript": "^4.4.2",
"use-file-picker": "^2.1.4",
"web-vitals": "^2.1.0" "web-vitals": "^2.1.0"
}, },
"scripts": { "scripts": {

View file

@ -1,5 +1,4 @@
import * as React from "react"; import * as React from "react";
import { useRef, useState } from "react";
import { import {
Button, Button,
styled, styled,
@ -10,12 +9,15 @@ import {
IconButton, IconButton,
Avatar, Avatar,
Box, Box,
Divider,
} from "@mui/material"; } from "@mui/material";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import EditSquareIcon from "@mui/icons-material/EditSquare"; import EditSquareIcon from "@mui/icons-material/EditSquare";
import "../styles/colors.css"; import "../styles/colors.css";
import "../styles/fonts.css"; import "../styles/fonts.css";
import "./changeAvatarDialog.css"; import "./changeAvatarDialog.css";
import ButtonPrimary from "./ButtonPrimary";
import { useFilePicker } from "use-file-picker";
const BootstrapDialog = styled(Dialog)(({ theme }) => ({ const BootstrapDialog = styled(Dialog)(({ theme }) => ({
"& .MuiDialogContent-root": { "& .MuiDialogContent-root": {
@ -27,45 +29,46 @@ const BootstrapDialog = styled(Dialog)(({ theme }) => ({
})); }));
export default function CustomizedDialogs() { export default function CustomizedDialogs() {
const { openFilePicker, filesContent, loading } = useFilePicker({
const inputFile = useRef<HTMLInputElement | null>(null); accept: ".png, .jpg, .jpeg",
multiple: false,
const openFileExplorer = () => { readAs: "DataURL",
// `current` points to the mounted file input element limitFilesConfig: { max: 1 },
if (inputFile.current) { });
inputFile.current.click();
}
};
const [selectedImage, setSelectedImage] = useState<File | null>(null);
const setImageURL = (selectedImage: File | null) => { const setImageURL = ({ newImage = false }: { newImage: boolean }) => {
if (selectedImage !== null) { if (newImage) {
return URL.createObjectURL(selectedImage); return filesContent[0].content;
} }
//TODO: If no image is selected, return the image already in the database or undefined // TODO: If no image is selected, return the image already in the database or undefined
return undefined; return undefined;
} };
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const handleClickOpen = () => { const handleClickOpen = () => {
setOpen(true); setOpen(true);
}; };
const handleClose = () => { const handleClose = () => {
setSelectedImage(null); // Reset the selected image when closing setImageURL({ newImage: false }); // Reset the selected image when closing
setOpen(false); setOpen(false);
}; };
const handleSaveChanges = () => { const handleSaveChanges = () => {
setOpen(false); setOpen(false);
} };
return ( return (
<React.Fragment> <React.Fragment>
<Button onClick={handleClickOpen}> <Button onClick={handleClickOpen}>
<Avatar <Avatar
alt="Username" alt="Username"
src={setImageURL(selectedImage)} // current code does not work yet
// TODO: If no image is selected, return the image already in the database or undefined
src={
filesContent.length > 0
? setImageURL({ newImage: true })
: undefined
}
className="profile-avatar" className="profile-avatar"
> >
U U
@ -76,7 +79,11 @@ export default function CustomizedDialogs() {
aria-labelledby="change-profile-picture-dialog" aria-labelledby="change-profile-picture-dialog"
open={open} open={open}
> >
<DialogTitle sx={{ m: 0, p: 2 }} id="change-profile-picture-dialog"> <DialogTitle
className="small-title orange-text"
sx={{ m: 1.5, p: 2 }}
id="change-profile-picture-dialog"
>
Change Profile Picture Change Profile Picture
</DialogTitle> </DialogTitle>
<IconButton <IconButton
@ -91,7 +98,8 @@ export default function CustomizedDialogs() {
> >
<CloseIcon /> <CloseIcon />
</IconButton> </IconButton>
<DialogContent dividers> <Divider variant="middle" className="divider" />
<DialogContent>
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
@ -103,32 +111,32 @@ export default function CustomizedDialogs() {
maxHeight: "30rem", maxHeight: "30rem",
}} }}
> >
{selectedImage && ( {filesContent.map((file) => (
<img <img
src={URL.createObjectURL(selectedImage)} alt={file.name}
alt="Profile Picture" src={file.content}
style={{maxWidth: "30rem", maxHeight: "30rem" , width: "100%", height: "100%", objectFit: "cover"}} style={{
/> maxWidth: "30rem",
)} maxHeight: "30rem",
width: "100%",
height: "100%",
objectFit: "cover",
}}
></img>
))}
</Box> </Box>
<IconButton aria-label="upload picture" onClick={openFileExplorer}> <div className="change-avatar-button">
<EditSquareIcon /> <IconButton
<input aria-label="upload picture"
type="file" onClick={() => openFilePicker()}
id="file" >
onChange={(event) => { <EditSquareIcon className="edit-icon" />
console.log(event.target.files ? [0] : undefined); // Log the selected file </IconButton>
if (event.target.files && event.target.files[0]) { </div>
setSelectedImage(event.target.files[0]); // Update the state with the selected file
}
}}
/>
</IconButton>
</DialogContent> </DialogContent>
<Divider variant="middle" className="divider" />
<DialogActions> <DialogActions>
<Button autoFocus onClick={handleSaveChanges}> <ButtonPrimary value="Save Changes" onClick={handleSaveChanges} />
Save changes
</Button>
</DialogActions> </DialogActions>
</BootstrapDialog> </BootstrapDialog>
</React.Fragment> </React.Fragment>

View file

@ -4,9 +4,43 @@
} }
.profile-image-large { .profile-image-large {
width: 20rem; max-width: 30rem;
height: 20rem; max-height: 30rem;
object-fit: cover; width: 100%;
border-radius: 8px; height: 100%;
object-fit: "cover";
} }
.css-10d30g3-MuiPaper-root-MuiDialog-paper {
background-color: var(--transparent-dark-blue);
backdrop-filter: blur(15px);
border-radius: 1rem;
}
.css-10d30g3-MuiPaper-root-MuiDialog-paper {
color: var(--Rotkehlchen-orange-default);
}
.profile-avatar {
width: 40px;
height: 40px;
background-color: aqua;
}
.css-53g0n7-MuiButtonBase-root-MuiIconButton-root {
color: var(--Rotkehlchen-orange-default);
}
.change-avatar-button {
display: flex;
justify-content: end;
align-items: end;
}
@media screen and (min-width: 768px) {
.profile-avatar {
width: 5rem;
height: 5rem;
background-color: var(--Rotkehlchen-yellow-default);
}
}

View file

@ -31,6 +31,7 @@
.numeral-data { .numeral-data {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
justify-content: space-around;
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
color: var(--Rotkehlchen-gray); color: var(--Rotkehlchen-gray);
@ -49,12 +50,6 @@
font-weight: 500; font-weight: 500;
} }
.profile-avatar {
width: 40px;
height: 40px;
background-color: aqua;
}
.profile-username { .profile-username {
font-weight: 700; font-weight: 700;
font-size: 1.2rem; font-size: 1.2rem;
@ -99,7 +94,7 @@
.numeral-data { .numeral-data {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
margin-top: 2rem; margin-top: 1rem;
font-weight: 700; font-weight: 700;
font-size: 2rem; font-size: 2rem;
} }
@ -115,10 +110,4 @@
flex-direction: column; flex-direction: column;
margin: 1rem; margin: 1rem;
} }
.profile-avatar {
width: 5rem;
height: 5rem;
background-color: aqua;
}
} }

View file

@ -26,3 +26,14 @@ body{
z-index: -1; z-index: -1;
background-attachment: fixed; background-attachment: fixed;
} }
.blue-background {
border-radius: 1rem;
background-color: var(--transparent-dark-blue);
backdrop-filter: blur(8px);
height: fit-content;
}
.edit-icon {
color: var(--Rotkehlchen-brown-light)
}

View file

@ -4830,6 +4830,13 @@ file-loader@^6.2.0:
loader-utils "^2.0.0" loader-utils "^2.0.0"
schema-utils "^3.0.0" schema-utils "^3.0.0"
file-selector@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-2.1.2.tgz#fe7c7ee9e550952dfbc863d73b14dc740d7de8b4"
integrity sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==
dependencies:
tslib "^2.7.0"
filelist@^1.0.4: filelist@^1.0.4:
version "1.0.4" version "1.0.4"
resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz" resolved "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz"
@ -9385,7 +9392,7 @@ tslib@^1.8.1:
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@^2.0.3: tslib@^2.0.3, tslib@^2.7.0:
version "2.8.1" version "2.8.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz" resolved "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
@ -9601,6 +9608,13 @@ url-parse@^1.5.3:
querystringify "^2.1.1" querystringify "^2.1.1"
requires-port "^1.0.0" requires-port "^1.0.0"
use-file-picker@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/use-file-picker/-/use-file-picker-2.1.4.tgz#2c005b005b627af30d2cfd1aeb3c01e952d390d1"
integrity sha512-b4lZiAWrXi/QNUjTv0Q+S0hVcSFXIC9c4EUcrnYtdPtgK3T6xfi01YLVamhoY0k9WM9Cg4KyxD1TtM1e8dzQAQ==
dependencies:
file-selector "^2.1.2"
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"