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

Add account data deletion functionality #738

Merged
merged 45 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
17ec25b
Add account data deletion functionality
mikozet Sep 25, 2023
5561724
Update remove data dialog
louilinn Sep 28, 2023
aa0f527
Add notification text
louilinn Sep 28, 2023
5ba3ad2
Update notification text further
louilinn Sep 28, 2023
7abe392
Make remove data component into button and change Settings view
louilinn Sep 28, 2023
87246bb
Change component name to ButtonDeleteProfile
louilinn Sep 28, 2023
f4e6852
Reroute to dashboard after delete profile is complete
louilinn Sep 28, 2023
efbb97b
Fix error notification
louilinn Sep 28, 2023
f5461d1
Seperate component DialogContentUploader from EditProfile
louilinn Oct 11, 2023
664e1e9
Break out DialogAvatarUpload from EditProfile
louilinn Oct 12, 2023
872cc68
Make AvatarUploader the universal uploader
louilinn Oct 12, 2023
681ba37
Fix organization indicator ring in onboarding avatar upload
louilinn Oct 12, 2023
4aec448
Skip blue border on avatar hover by default
louilinn Oct 12, 2023
99a4f88
Rename profilePic to avatar
louilinn Oct 12, 2023
4698d3e
Unionize setAvatarUploadUrl for camera upload and file upload
louilinn Oct 12, 2023
b673751
Keep Cancel button and add text button for deleteprofile
louilinn Oct 12, 2023
77576e1
Change Dialog component and use it in EditProfile
louilinn Oct 12, 2023
f3d2ff9
Edit Dialog Components
louilinn Oct 12, 2023
de1e965
Make cancel method for later deleting uploaded avatar
louilinn Oct 12, 2023
be7efe7
Fix props to AvatarUploader
louilinn Oct 17, 2023
34e8fa1
Update core on trial
louilinn Oct 17, 2023
b71f8ac
Use latest core version with delete profile data functionality
louilinn Oct 17, 2023
0b07569
Delete old todo note in TabNavigationAction (done)
louilinn Oct 17, 2023
b4da4df
Call core.user.delete on delete button
louilinn Oct 17, 2023
2b21d99
Call core.user.delete on delete button fix
louilinn Oct 17, 2023
4aa6d7f
Add core methods for upload correctly and use in Upload
louilinn Oct 26, 2023
317b7fe
Remove console logs
louilinn Oct 26, 2023
949104a
Fix useSelector import
louilinn Oct 26, 2023
f5f75f0
Delete old avatar on save new profile
louilinn Oct 26, 2023
a8ee318
Pass avatarUploadUrl to upload components
louilinn Oct 27, 2023
93a13b9
Fix avatar upload and delete
louilinn Oct 27, 2023
fdb26e3
Make upload onboarding continue possible
louilinn Oct 27, 2023
584cfeb
Install core patch
louilinn Oct 27, 2023
5a70f8f
Fix onboarding organization continue after upload
louilinn Oct 27, 2023
65243df
Fix onboarding organization continue after upload
louilinn Oct 27, 2023
9e93df4
Make Delete button in Settings outline
louilinn Oct 27, 2023
d39686b
Update deletion modal content and FAQ link
louilinn Oct 27, 2023
e8b7de9
Fix count unread news in unread activities
louilinn Oct 27, 2023
92df2bb
Show avatar in next steps after uploading it in onboarding
louilinn Nov 7, 2023
51c4cc2
Fix lint
louilinn Nov 7, 2023
a15336d
Make avatar upload persists when going backwards in onboarding
louilinn Nov 7, 2023
ee4f7be
Fix loading state for file upload
louilinn Nov 7, 2023
16734d8
Add option to edit profile in profile deletion modal from settings
louilinn Nov 7, 2023
7f6d0a2
Fix optional function call handleUpload
louilinn Nov 9, 2023
74d929a
Delete avatar image when deleting user entry in delete profile
louilinn Nov 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
"optionCamera": "Camera",
"optionUpload": "Upload",
"optionFile": "Gallery",
"titleCancel": "Do you want to save changes to your profile?"
"titleCancel": "Do you want to save your changes to your profile?"
},
"ErrorCodes": {
"CoreErrorInsufficientFunds": "Please send some Circles to this account if you want to perform any transaction",
Expand Down Expand Up @@ -343,6 +343,20 @@
},
"bodyUndeployedToken": "Account not verified"
},
"ButtonDeleteProfile": {
"titleText": "Are you sure you want to delete your profile data?",
"bodyText": "This action will delete your profile picture, username and email from our databases.",
"bodyText2": "However, this action will not delete historical transaction data or trust interactions.",
"bodyText3": "After deletion you can chose to end session through Settings. If you do not log in within 90 days, your UBI payouts will be stopped forever.",
"bodyText4": "Please note that data deletion will not happen across wallets. You will have to do profile data deletion your personal wallet and any shared wallets separately before ending session.",
"btnText": "Delete Profile Data",
"confirmationDelete": "Delete",
"confirmationCancel": "Cancel",
"linkEditProfile": "Edit Profile Instead",
"notificationError": "Oops, something went wrong. Try again!",
"notificationSuccess": "The profile data of this wallet has successfully been deleted. You can end session in Settings.",
"readMore": "Read more"
},
"QRCodeScanner": {
"notificationError": "Could not open QR code scanner: {error}."
},
Expand Down
4,386 changes: 3,725 additions & 661 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"webpack-dev-server": "^3.11.3"
},
"dependencies": {
"@circles/core": "^4.8.0",
"@circles/core": "^4.9.1",
"@circles/timecircles": "^1.0.6",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
Expand Down
25 changes: 15 additions & 10 deletions src/components/ActivityIcon.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,25 @@ const DashboardActivityIcon = () => {
});
});

// Count how many activities we haven't seen yet
const count = CATEGORIES.reduce((acc, category) => {
return (
acc +
categories[category].activities.reduce((itemAcc, activity) => {
return activity.createdAt > lastSeenAt ? itemAcc + 1 : itemAcc;
}, 0)
);
}, 0);

const countNews = news.activities.reduce((itemAcc, activity) => {
return activity.createdAt > lastSeenAt ? itemAcc + 1 : itemAcc;
}, 0);

// Count how many activities we haven't seen yet
const count =
CATEGORIES.reduce((acc, category) => {
/* eslint-disable no-console */
console.log(category);
/* eslint-enable no-console */

return (
acc +
categories[category].activities.reduce((itemAcc, activity) => {
return activity.createdAt > lastSeenAt ? itemAcc + 1 : itemAcc;
}, 0)
);
}, 0) + countNews;

return (
<IconButton
aria-label="Activities"
Expand Down
26 changes: 21 additions & 5 deletions src/components/Avatar.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ const useStyles = makeStyles((theme) => ({
},
circleGrey: {
background: theme.custom.colors.greyHover,
border: `2px solid ${theme.custom.colors.blue100}`,
position: 'absolute',
left: '-1px',
top: '-1px',
Expand All @@ -45,6 +44,9 @@ const useStyles = makeStyles((theme) => ({
opacity: 0,
transition: 'opacity 0.1s ease-in-out',
},
circleBorder: {
border: `2px solid ${theme.custom.colors.blue100}`,
},
isHovered: {
opacity: 1,
},
Expand All @@ -55,6 +57,8 @@ const useStyles = makeStyles((theme) => ({

const Avatar = ({
address,
children,
showIndicatorRing,
size = 'small',
url,
useCache,
Expand All @@ -78,13 +82,23 @@ const Avatar = ({
const sizePixelRing = sizePixelAvatar * ORGANIZATION_RING_MULTIPLIER;
const initials = username.slice(0, 2) === '0x' ? null : username.slice(0, 2);

const backupImage = () => {
if (avatarUrl && initials) {
return initials.toUpperCase();
}
if (address) {
return <Jazzicon address={address} size={sizePixelAvatar} />;
}
return children;
};

return (
<Box
className={classes.avatarContainer}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
{isOrganization && (
{(isOrganization || showIndicatorRing) && (
<Box className={classes.organizationIndicator}>
<GroupWalletCircleSVG width={sizePixelRing} />
</Box>
Expand All @@ -94,6 +108,8 @@ const Avatar = ({
<Box
className={clsx(classes.circleGrey, {
[classes.isHovered]: isHovered || withClickEffect,
[classes.circleBorder]:
(isHovered || withClickEffect) && !hidePlusIcon,
})}
style={{
width: sizePixelAvatar + 2,
Expand All @@ -118,17 +134,17 @@ const Avatar = ({
}}
{...props}
>
{avatarUrl && initials
? initials.toUpperCase()
: address && <Jazzicon address={address} size={sizePixelAvatar} />}
{backupImage()}
</MuiAvatar>
</Box>
);
};

Avatar.propTypes = {
address: PropTypes.string,
children: PropTypes.node,
hidePlusIcon: PropTypes.bool,
showIndicatorRing: PropTypes.bool,
size: PropTypes.string,
url: PropTypes.string,
useCache: PropTypes.bool,
Expand Down
3 changes: 3 additions & 0 deletions src/components/AvatarHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const useStyles = makeStyles(() => ({
const AvatarHeader = ({
hideImage,
hidePlusIcon,
url,
username,
useCache = true,
withClickEffect,
Expand Down Expand Up @@ -61,6 +62,7 @@ const AvatarHeader = ({
className={classes.avatarContainer}
hidePlusIcon={hidePlusIcon}
size={'smallXl'}
url={url}
useCache={useCache}
withClickEffect={withClickEffect}
withHoverEffect={withHoverEffect}
Expand All @@ -80,6 +82,7 @@ const AvatarHeader = ({
AvatarHeader.propTypes = {
hideImage: PropTypes.bool,
hidePlusIcon: PropTypes.bool,
url: PropTypes.string,
useCache: PropTypes.bool,
username: PropTypes.string,
withClickEffect: PropTypes.bool,
Expand Down
142 changes: 44 additions & 98 deletions src/components/AvatarUploader.js
Original file line number Diff line number Diff line change
@@ -1,137 +1,83 @@
import { Avatar, Box, CircularProgress, Typography } from '@mui/material';
import { Box, CircularProgress } from '@mui/material';
import makeStyles from '@mui/styles/makeStyles';
import mime from 'mime/lite';
import PropTypes from 'prop-types';
import React, { Fragment, useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import React, { Fragment, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';

import GroupWalletCircleSVG from '%/images/organization-indicator.svg';
import core from '~/services/core';
import translate from '~/services/locale';
import notify, { NotificationsTypes } from '~/store/notifications/actions';

const IMAGE_FILE_TYPES = ['jpg', 'jpeg', 'png'];
import Avatar from '~/components/Avatar';
import DialogAvatarUpload from '~/components/DialogAvatarUpload';
import { IconPlus } from '~/styles/icons';

const useStyles = makeStyles((theme) => ({
avatarUpload: {
margin: '0 auto',
width: theme.custom.components.avatarUploader,
height: theme.custom.components.avatarUploader,
color: theme.palette.text.primary,
fontSize: '30px',
fontWeight: theme.typography.fontWeightMedium,
backgroundColor: theme.custom.colors.white,
border: `1px solid ${theme.palette.text.primary}`,
cursor: 'pointer',
position: 'relative',
zIndex: theme.zIndex.layer1,
},
indicatorContainer: {
position: 'absolute',
zIndex: theme.zIndex.layer1,
top: '-2px',
left: 0,
border: `1px solid ${theme.palette.secondary.main}`,
},
avatarUploaderContainer: {
width: theme.custom.components.avatarUploader,
height: theme.custom.components.avatarUploader,
margin: '0 auto',
display: 'inline-flex',
position: 'relative',
flexShrink: 0,
verticalAlign: 'middle',
},
plusIcon: {
fontSize: '16px',
zIndex: theme.zIndex.layer2,
},
}));

const AvatarUploader = ({
handleUpload,
onLoadingChange,
onUpload,
value,
shouldHaveIndicator,
showIndicatorRing,
presetUrl,
}) => {
const classes = useStyles();

const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const fileInputElem = useRef();

const handleUploadClick = (event) => {
event.preventDefault();
fileInputElem.current.click();
};
const [isOpenDialogUploadInfo, setIsOpenDialogUploadInfo] = useState(false);
const [avatarUploadUrl, setAvatarUploadUrl] = useState(presetUrl);
const isLoading = useSelector((state) => state.isLoading);

useEffect(() => {
onLoadingChange(isLoading);
}, [isLoading, onLoadingChange]);

const handleChange = async (event) => {
setIsLoading(true);

const { files } = event.target;
if (files.length === 0) {
return;
}

try {
const result = await core.utils.requestAPI({
path: ['uploads', 'avatar'],
method: 'POST',
data: [...files].reduce((acc, file) => {
acc.append('files', file, file.name);
return acc;
}, new FormData()),
});

onUpload(result.data.url);
} catch (error) {
dispatch(
notify({
text: (
<Typography classes={{ root: 'body4_white' }} variant="body4">
{translate('AvatarUploader.errorAvatarUpload')}
</Typography>
),
type: NotificationsTypes.ERROR,
}),
);
}

setIsLoading(false);
};

const fileTypesStr = IMAGE_FILE_TYPES.map((ext) => {
return mime.getType(ext);
}).join(',');

return (
<Fragment>
<Box className={classes.avatarUploaderContainer}>
{shouldHaveIndicator && (
<Box className={classes.indicatorContainer}>
<GroupWalletCircleSVG width={94} />
</Box>
)}
<DialogAvatarUpload
avatarUploadUrl={avatarUploadUrl}
handleClose={() => setIsOpenDialogUploadInfo(false)}
handleUpload={handleUpload}
isOpen={isOpenDialogUploadInfo}
setAvatarUploadUrl={setAvatarUploadUrl}
/>
<Box
className={classes.avatarUploaderContainer}
onClick={() => setIsOpenDialogUploadInfo(true)}
>
<Avatar
className={classes.avatarUpload}
src={isLoading ? null : value}
onClick={handleUploadClick}
showIndicatorRing={showIndicatorRing}
size="medium"
url={avatarUploadUrl}
withClickEffect={isOpenDialogUploadInfo}
withHoverEffect
>
{isLoading ? <CircularProgress /> : '+'}
{isLoading ? (
<CircularProgress />
) : (
<IconPlus className={classes.plusIcon} />
)}
</Avatar>
<input
accept={fileTypesStr}
ref={fileInputElem}
style={{ display: 'none' }}
type="file"
onChange={handleChange}
/>
</Box>
</Fragment>
);
};

AvatarUploader.propTypes = {
handleUpload: PropTypes.func,
onLoadingChange: PropTypes.func.isRequired,
onUpload: PropTypes.func.isRequired,
shouldHaveIndicator: PropTypes.bool,
value: PropTypes.string,
presetUrl: PropTypes.string,
showIndicatorRing: PropTypes.bool,
};

export default AvatarUploader;
Loading