Skip to content
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

619/refactor image upload button #680

Open
wants to merge 11 commits into
base: Development
Choose a base branch
from
116 changes: 83 additions & 33 deletions src/components/Profile/ProfileImageField.jsx
Copy link
Contributor

@leekahung leekahung Sep 21, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nitpick, could we have a small text right under the image icon (perhaps in white text) to let users know what it's for? For someone that's not tech-savvy, it's not immediately clear that I would need to click don't the image to change the image.

Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import ConfirmationModal from '../Modals/ConfirmationModal';
*
* @memberof Profile
* @name ProfileImageField
* @param {object} Props - Props used for NewMessage
* @param {object} Props - Props used for ProfileImageField
* @param {() => void} Props.loadProfileData - Handler function for setting local
* state for profile card in PASS
* @param {object} [Props.contactProfile] - Contact object with data from profile
* or null if user profile is selected
* @returns {React.JSX.Element} React component for NewMessage
* @returns {React.JSX.Element} React component for ProfileImageField
*/
const ProfileImageField = ({ loadProfileData, contactProfile }) => {
const { addNotification } = useNotification();
Expand All @@ -34,6 +34,7 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => {
const [profileImg, setProfileImg] = useState(localStorage.getItem('profileImage'));
const [processing, setProcessing] = useState(false);
const [showConfirmationModal, setShowConfirmationModal] = useState(false);
const [hover, setHover] = useState(false);

const handleProfileImage = async (event) => {
if (event.target.files[0].size > 1 * 1000 * 1024) {
Expand Down Expand Up @@ -69,46 +70,95 @@ const ProfileImageField = ({ loadProfileData, contactProfile }) => {
setShowConfirmationModal(true);
};

const iconButtonStyling = {
position: 'absolute',
width: '100%',
height: '100%',
top: '50%',
left: '50%',
opacity: 0.8,
transform: 'translate(-50%, -50%)',
borderRadius: '50%',
cursor: 'pointer',
border: 'none',
color: '#FFFFFF',
backgroundColor: '#545454',
'&:hover': {
backgroundColor: '#545454'
}
};

return (
<Box
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
padding: '20px',
gap: '10px'
padding: '20px'
}}
>
<Avatar
src={contactProfile ? contactProfile.profileImage : profileImg}
alt="PASS profile"
sx={{ height: '100px', width: '100px', objectFit: 'contain' }}
/>
{!contactProfile &&
(profileImg ? (
<Button
variant="outlined"
color="error"
sx={{ width: '150px' }}
onClick={handleSelectRemoveProfileImg}
endIcon={<HideImageIcon />}
>
Remove Img
</Button>
) : (
<Button
variant="outlined"
component="label"
color="primary"
onChange={handleProfileImage}
endIcon={<ImageIcon />}
sx={{ width: '150px' }}
>
Choose Img
<input type="file" hidden accept=".gif, .png, .jpeg, .jpg, .webp" />
</Button>
))}
<Box
position="relative"
display="inline-block"
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<Avatar
src={contactProfile?.profileImage || profileImg || ''}
alt="PASS profile"
sx={{
height: '100px',
width: '100px'
}}
/>
{hover && (
<>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'rgba(0, 0, 0, 0.5)',
borderRadius: '50%'
}}
/>
{contactProfile || profileImg ? (
<Button
data-testid="deleteProfilePictureIcon"
aria-label="delete-profile-picture"
component="label"
variant="contained"
startIcon={<HideImageIcon />}
onClick={handleSelectRemoveProfileImg}
sx={iconButtonStyling}
>
Delete
</Button>
) : (
<Button
data-testid="uploadProfilePictureIcon"
aria-label="upload-profile-picture"
component="label"
variant="contained"
startIcon={<ImageIcon />}
sx={iconButtonStyling}
>
Upload
<input
type="file"
hidden
accept=".gif, .png, .jpeg, .jpg, .webp"
onChange={handleProfileImage}
/>
</Button>
)}
</>
)}
</Box>

<ConfirmationModal
showModal={showConfirmationModal}
setShowModal={setShowConfirmationModal}
Expand Down
46 changes: 21 additions & 25 deletions test/components/Profile/ProfileImageField.test.jsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
import React from 'react';
import { render } from '@testing-library/react';
import { fireEvent, render, screen } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import { ProfileImageField } from '../../../src/components/Profile';
import '@testing-library/jest-dom/extend-expect';

const MockProfileComponent = ({ noUserImage, mockContactProfile }) => {
if (!noUserImage) {
if (noUserImage) {
localStorage.removeItem('profileImage');
} else {
localStorage.setItem('profileImage', 'https://example.com/user.png');
}
return <ProfileImageField contactProfile={mockContactProfile} />;
};

describe('ProfileImageField', () => {
it('renders Choose Img button and PersonIcon if contactProfile is null and user has no profile img', () => {
const { queryByRole, queryByTestId } = render(
it('renders PersonIcon if contactProfile is null and user has no profile image', () => {
const { queryByTestId } = render(
<MockProfileComponent noUserImage mockContactProfile={null} />
);
const buttonElement = queryByRole('button');
expect(buttonElement.textContent).toBe('Choose Img');

const svgElement = queryByTestId('PersonIcon');
expect(svgElement).not.toBeNull();
});

it('renders Remove Img button and image if contactProfile is null, but user has profile image', () => {
it('renders image if contactProfile is null but user has profile image', () => {
const { queryByRole, queryByTestId } = render(
<MockProfileComponent mockContactProfile={null} />
);
const buttonElement = queryByRole('button');
expect(buttonElement.textContent).toBe('Remove Img');

const muiAvatar = queryByRole('img');
expect(muiAvatar).toHaveAttribute('src', 'https://example.com/user.png');
Expand All @@ -37,27 +35,25 @@ describe('ProfileImageField', () => {
expect(svgElement).toBeNull();
});

it('renders no button with PersonIcon if contactProfile is not null and has no profile image', () => {
const { queryByRole, queryByTestId } = render(<MockProfileComponent mockContactProfile={{}} />);
const buttonElement = queryByRole('button');
expect(buttonElement).toBeNull();
it('renders upload profile picture icon when user hovers over avatar with no profile image', async () => {
render(<MockProfileComponent noUserImage mockContactProfile={null} />);

const svgElement = queryByTestId('PersonIcon');
expect(svgElement).not.toBeNull();
const avatar = screen.queryByTestId('PersonIcon');
expect(avatar).not.toBeNull();
expect(screen.queryByTestId('uploadProfilePictureIcon')).toBeNull();

fireEvent.mouseEnter(avatar);
expect(screen.getByTestId('uploadProfilePictureIcon')).not.toBeNull();
});

it('renders no button with image if contactProfile is not null and has profile image', () => {
it('renders deleteProfilePictureIcon when user hovers over avatar with profile image uploaded', () => {
const mockContactProfile = { profileImage: 'https://example.com/client.png' };
const { queryByRole, queryByTestId } = render(
<MockProfileComponent mockContactProfile={mockContactProfile} />
);
const buttonElement = queryByRole('button');
expect(buttonElement).toBeNull();
render(<MockProfileComponent mockContactProfile={mockContactProfile} />);

const muiAvatar = queryByRole('img');
expect(muiAvatar).toHaveAttribute('src', 'https://example.com/client.png');
const avatar = screen.getByAltText('PASS profile');
expect(screen.queryByTestId('deleteProfilePictureIcon')).toBeNull();

const svgElement = queryByTestId('PersonIcon');
expect(svgElement).toBeNull();
fireEvent.mouseEnter(avatar);
expect(screen.getByTestId('deleteProfilePictureIcon')).not.toBeNull();
});
});
Loading