add subscription details to sidebar

This commit is contained in:
Abhinav 2022-04-28 17:40:30 +05:30
parent fa802e253c
commit fde43182ac
6 changed files with 297 additions and 281 deletions

View file

@ -0,0 +1,95 @@
import React from 'react';
import {
Box,
CircularProgress,
LinearProgress,
Typography,
} from '@mui/material';
import { FlexWrapper, SpaceBetweenFlex } from 'components/Container';
import { UserDetails } from 'types/user';
import constants from 'utils/strings/constants';
import { formatDateShort } from 'utils/time';
import { convertBytesToHumanReadable } from 'utils/billing';
interface Iprops {
userDetails: UserDetails;
}
export default function SubscriptionDetails({ userDetails }: Iprops) {
return (
<Box
display="flex"
flexDirection={'column'}
height={160}
bgcolor="accent.main"
borderRadius={'8px'}
position={'relative'}>
{userDetails ? (
<>
<Box
display="flex"
flexDirection={'column'}
padding="16px"
height="96px">
<SpaceBetweenFlex>
<Typography variant="subtitle2">
Current Plan
</Typography>
<Typography
variant="subtitle2"
sx={{ color: 'text.secondary' }}>
{`${constants.ENDS} ${formatDateShort(
userDetails.subscription.expiryTime / 1000
)}`}
</Typography>
</SpaceBetweenFlex>
<Typography
sx={{ fontWeight: '700', fontSize: '24px' }}>
{convertBytesToHumanReadable(
userDetails.subscription.storage,
0
)}
</Typography>
</Box>
<Box
position={'absolute'}
right="17px"
top="10px"
component={'img'}
src="/images/locker.png"
/>
<Box
position={'relative'}
zIndex="100"
height="64px"
borderRadius={'0 0 8px 8px'}
bgcolor="accent.dark"
padding="16px">
<LinearProgress
sx={{ bgcolor: 'text.secondary' }}
variant="determinate"
value={70}
/>
<SpaceBetweenFlex style={{ marginTop: '8px' }}>
<Typography variant="caption">
{`${convertBytesToHumanReadable(
userDetails.usage,
1
)} of ${convertBytesToHumanReadable(
userDetails.subscription.storage,
0
)}`}
</Typography>
<Typography variant="caption">
{`${userDetails.fileCount} Photos`}
</Typography>
</SpaceBetweenFlex>
</Box>
</>
) : (
<FlexWrapper style={{ flex: '1', justifyContent: 'center' }}>
<CircularProgress />
</FlexWrapper>
)}
</Box>
);
}

View file

@ -1,16 +1,9 @@
import React, { useContext, useEffect, useState } from 'react';
import constants from 'utils/strings/constants';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import { LS_KEYS, setData } from 'utils/storage/localStorage';
import { getToken } from 'utils/common/key';
import { getEndpoint } from 'utils/common/apiUtil';
import {
isSubscriptionActive,
getUserSubscription,
isOnFreePlan,
isSubscriptionCancelled,
isSubscribed,
convertBytesToHumanReadable,
} from 'utils/billing';
import { convertBytesToHumanReadable } from 'utils/billing';
import isElectron from 'is-electron';
import { Collection } from 'types/collection';
@ -19,24 +12,25 @@ import LinkButton from '../pages/gallery/LinkButton';
import { downloadApp } from 'utils/common';
import { getUserDetails, logoutUser } from 'services/userService';
import { SetDialogMessage } from '../MessageDialog';
import EnteSpinner from '../EnteSpinner';
import RecoveryKeyModal from '../RecoveryKeyModal';
import TwoFactorModal from '../TwoFactorModal';
import ExportModal from '../ExportModal';
import { GalleryContext } from 'pages/gallery';
import InProgressIcon from '../icons/InProgressIcon';
import exportService from 'services/exportService';
import { Subscription } from 'types/billing';
import { PAGES } from 'constants/pages';
import { ARCHIVE_SECTION, TRASH_SECTION } from 'constants/collection';
import FixLargeThumbnails from '../FixLargeThumbnail';
import { SetLoading } from 'types/gallery';
import { downloadAsFile } from 'utils/file';
import { getUploadLogs, logUploadInfo } from 'utils/upload';
import { Box, Divider, Drawer, Typography } from '@mui/material';
import { Divider, Drawer, Typography } from '@mui/material';
import { default as MuiStyled } from '@mui/styled-engine';
import { Button } from 'react-bootstrap';
import ThemeToggler from './ThemeToggler';
import SubscriptionDetails from './SubscriptionDetails';
import { SpaceBetweenFlex } from 'components/Container';
import { UserDetails } from 'types/user';
import { getLocalUserDetails } from 'utils/user';
interface Props {
collections: Collection[];
setDialogMessage: SetDialogMessage;
@ -50,7 +44,7 @@ export enum THEMES {
const DrawerSidebar = MuiStyled(Drawer)(() => ({
'& .MuiPaper-root': {
width: '300px',
width: '320px',
padding: '20px',
},
}));
@ -61,12 +55,9 @@ const WidderDivider = MuiStyled(Divider)(() => ({
}));
export default function Sidebar(props: Props) {
const [usage, SetUsage] = useState<string>(null);
const [user, setUser] = useState(null);
const [subscription, setSubscription] = useState<Subscription>(null);
const [userDetails, setUserDetails] = useState<UserDetails>(null);
useEffect(() => {
setUser(getData(LS_KEYS.USER));
setSubscription(getUserSubscription());
setUserDetails(getLocalUserDetails());
}, []);
const [isOpen, setIsOpen] = useState(false);
const [recoverModalView, setRecoveryModalView] = useState(false);
@ -78,18 +69,9 @@ export default function Sidebar(props: Props) {
useEffect(() => {
const main = async () => {
if (!isOpen) {
return;
}
const userDetails = await getUserDetails();
setUser({ ...user, email: userDetails.email });
SetUsage(convertBytesToHumanReadable(userDetails.usage));
setSubscription(userDetails.subscription);
setData(LS_KEYS.USER, {
...getData(LS_KEYS.USER),
email: userDetails.email,
});
setData(LS_KEYS.SUBSCRIPTION, userDetails.subscription);
setUserDetails(userDetails);
setData(LS_KEYS.USER_DETAILS, userDetails);
};
main();
}, [isOpen]);
@ -138,10 +120,6 @@ export default function Sidebar(props: Props) {
};
const router = useRouter();
function onManageClick() {
setIsOpen(false);
galleryContext.showPlanSelectorModal();
}
return (
<DrawerSidebar
@ -154,256 +132,183 @@ export default function Sidebar(props: Props) {
{constants.ENTE}
</Typography>
<WidderDivider />
<Box
display="flex"
mb="10px"
mt="10px"
alignItems="center"
justifyContent={'space-between'}>
<Typography>{user?.email}</Typography>
<SpaceBetweenFlex style={{ marginBottom: '20px' }}>
<Typography>{userDetails?.email}</Typography>
<ThemeToggler theme={theme} setTheme={setTheme} />
</Box>
<div style={{}}></div>
</SpaceBetweenFlex>
<SubscriptionDetails userDetails={userDetails} />
<WidderDivider />
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
galleryContext.setActiveCollection(ARCHIVE_SECTION);
setIsOpen(false);
}}>
{constants.ARCHIVE}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
galleryContext.setActiveCollection(TRASH_SECTION);
setIsOpen(false);
}}>
{constants.TRASH}
</LinkButton>
<>
<RecoveryKeyModal
show={recoverModalView}
onHide={() => setRecoveryModalView(false)}
somethingWentWrong={() =>
props.setDialogMessage({
title: constants.ERROR,
content:
constants.RECOVER_KEY_GENERATION_FAILED,
close: { variant: 'danger' },
})
}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => setRecoveryModalView(true)}>
{constants.DOWNLOAD_RECOVERY_KEY}
</LinkButton>
</>
<>
<TwoFactorModal
show={twoFactorModalView}
onHide={() => setTwoFactorModalView(false)}
setDialogMessage={props.setDialogMessage}
closeSidebar={() => setIsOpen(false)}
setLoading={props.setLoading}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => setTwoFactorModalView(true)}>
{constants.TWO_FACTOR}
</LinkButton>
</>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
router.push(PAGES.CHANGE_PASSWORD);
}}>
{constants.CHANGE_PASSWORD}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
router.push(PAGES.CHANGE_EMAIL);
}}>
{constants.UPDATE_EMAIL}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
router.push(PAGES.DEDUPLICATE);
}}>
{constants.DEDUPLICATE_FILES}
</LinkButton>
<Divider />
<>
<FixLargeThumbnails
isOpen={fixLargeThumbsView}
hide={() => setFixLargeThumbsView(false)}
show={() => setFixLargeThumbsView(true)}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => setFixLargeThumbsView(true)}>
{constants.FIX_LARGE_THUMBNAILS}
</LinkButton>
</>
<LinkButton
style={{ marginTop: '30px' }}
onClick={openFeedbackURL}>
{constants.REQUEST_FEATURE}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => initiateEmail('contact@ente.io')}>
{constants.SUPPORT}
</LinkButton>
<>
<ExportModal
show={exportModalView}
onHide={() => setExportModalView(false)}
usage={convertBytesToHumanReadable(
userDetails?.usage ?? 0
)}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={exportFiles}>
<div style={{ display: 'flex' }}>
{constants.EXPORT}
<div style={{ width: '20px' }} />
{exportService.isExportInProgress() && (
<InProgressIcon />
)}
</div>
</LinkButton>
</>
<Divider />
<LinkButton
variant="danger"
style={{ marginTop: '30px' }}
onClick={() =>
props.setDialogMessage({
title: `${constants.CONFIRM} ${constants.LOGOUT}`,
content: constants.LOGOUT_MESSAGE,
staticBackdrop: true,
proceed: {
text: constants.LOGOUT,
action: logoutUser,
variant: 'danger',
},
close: { text: constants.CANCEL },
})
}>
{constants.LOGOUT}
</LinkButton>
<LinkButton
variant="danger"
style={{ marginTop: '30px' }}
onClick={() =>
props.setDialogMessage({
title: `${constants.DELETE_ACCOUNT}`,
content: constants.DELETE_ACCOUNT_MESSAGE(),
staticBackdrop: true,
proceed: {
text: constants.DELETE_ACCOUNT,
action: () => {
initiateEmail('account-deletion@ente.io');
},
variant: 'danger',
},
close: { text: constants.CANCEL },
})
}>
{constants.DELETE_ACCOUNT}
</LinkButton>
<Divider style={{ marginTop: '36px' }} />
<div
style={{
flex: 1,
overflow: 'auto',
outline: 'none',
paddingTop: '0',
}}>
<div style={{ outline: 'none' }}>
<div style={{ display: 'flex' }}>
<h5 style={{ margin: '4px 0 12px 2px' }}>
{constants.SUBSCRIPTION_PLAN}
</h5>
</div>
<div style={{ color: '#959595' }}>
{isSubscriptionActive(subscription) ? (
isOnFreePlan(subscription) ? (
constants.FREE_SUBSCRIPTION_INFO(
subscription?.expiryTime
)
) : isSubscriptionCancelled(subscription) ? (
constants.RENEWAL_CANCELLED_SUBSCRIPTION_INFO(
subscription?.expiryTime
)
) : (
constants.RENEWAL_ACTIVE_SUBSCRIPTION_INFO(
subscription?.expiryTime
)
)
) : (
<p>{constants.SUBSCRIPTION_EXPIRED}</p>
)}
<Button
variant="outline-success"
block
size="sm"
onClick={onManageClick}>
{isSubscribed(subscription)
? constants.MANAGE
: constants.SUBSCRIBE}
</Button>
</div>
</div>
<div style={{ outline: 'none', marginTop: '30px' }} />
<div>
<h5 style={{ marginBottom: '12px' }}>
{constants.USAGE_DETAILS}
</h5>
<div style={{ color: '#959595' }}>
{usage ? (
constants.USAGE_INFO(
usage,
convertBytesToHumanReadable(
subscription?.storage
)
)
) : (
<div style={{ textAlign: 'center' }}>
<EnteSpinner
style={{
borderWidth: '2px',
width: '20px',
height: '20px',
}}
/>
</div>
)}
</div>
</div>
<Divider />
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
galleryContext.setActiveCollection(ARCHIVE_SECTION);
setIsOpen(false);
}}>
{constants.ARCHIVE}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
galleryContext.setActiveCollection(TRASH_SECTION);
setIsOpen(false);
}}>
{constants.TRASH}
</LinkButton>
<>
<RecoveryKeyModal
show={recoverModalView}
onHide={() => setRecoveryModalView(false)}
somethingWentWrong={() =>
props.setDialogMessage({
title: constants.ERROR,
content:
constants.RECOVER_KEY_GENERATION_FAILED,
close: { variant: 'danger' },
})
}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => setRecoveryModalView(true)}>
{constants.DOWNLOAD_RECOVERY_KEY}
</LinkButton>
</>
<>
<TwoFactorModal
show={twoFactorModalView}
onHide={() => setTwoFactorModalView(false)}
setDialogMessage={props.setDialogMessage}
closeSidebar={() => setIsOpen(false)}
setLoading={props.setLoading}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => setTwoFactorModalView(true)}>
{constants.TWO_FACTOR}
</LinkButton>
</>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
router.push(PAGES.CHANGE_PASSWORD);
}}>
{constants.CHANGE_PASSWORD}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
router.push(PAGES.CHANGE_EMAIL);
}}>
{constants.UPDATE_EMAIL}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => {
router.push(PAGES.DEDUPLICATE);
}}>
{constants.DEDUPLICATE_FILES}
</LinkButton>
<Divider />
<>
<FixLargeThumbnails
isOpen={fixLargeThumbsView}
hide={() => setFixLargeThumbsView(false)}
show={() => setFixLargeThumbsView(true)}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => setFixLargeThumbsView(true)}>
{constants.FIX_LARGE_THUMBNAILS}
</LinkButton>
</>
<LinkButton
style={{ marginTop: '30px' }}
onClick={openFeedbackURL}>
{constants.REQUEST_FEATURE}
</LinkButton>
<LinkButton
style={{ marginTop: '30px' }}
onClick={() => initiateEmail('contact@ente.io')}>
{constants.SUPPORT}
</LinkButton>
<>
<ExportModal
show={exportModalView}
onHide={() => setExportModalView(false)}
usage={usage}
/>
<LinkButton
style={{ marginTop: '30px' }}
onClick={exportFiles}>
<div style={{ display: 'flex' }}>
{constants.EXPORT}
<div style={{ width: '20px' }} />
{exportService.isExportInProgress() && (
<InProgressIcon />
)}
</div>
</LinkButton>
</>
<Divider />
<LinkButton
variant="danger"
style={{ marginTop: '30px' }}
onClick={() =>
props.setDialogMessage({
title: `${constants.CONFIRM} ${constants.LOGOUT}`,
content: constants.LOGOUT_MESSAGE,
staticBackdrop: true,
proceed: {
text: constants.LOGOUT,
action: logoutUser,
variant: 'danger',
},
close: { text: constants.CANCEL },
})
}>
{constants.LOGOUT}
</LinkButton>
<LinkButton
variant="danger"
style={{ marginTop: '30px' }}
onClick={() =>
props.setDialogMessage({
title: `${constants.DELETE_ACCOUNT}`,
content: constants.DELETE_ACCOUNT_MESSAGE(),
staticBackdrop: true,
proceed: {
text: constants.DELETE_ACCOUNT,
action: () => {
initiateEmail(
'account-deletion@ente.io'
);
},
variant: 'danger',
},
close: { text: constants.CANCEL },
})
}>
{constants.DELETE_ACCOUNT}
</LinkButton>
<Divider style={{ marginTop: '36px' }} />
<div
style={{
marginTop: '40px',
width: '100%',
}}
/>
<div
style={{
marginTop: '30px',
fontSize: '14px',
textAlign: 'center',
color: 'grey',
cursor: 'pointer',
}}
onClick={downloadUploadLogs}>
{constants.DOWNLOAD_UPLOAD_LOGS}
</div>
marginTop: '40px',
width: '100%',
}}
/>
<div
style={{
marginTop: '30px',
fontSize: '14px',
textAlign: 'center',
color: 'grey',
cursor: 'pointer',
}}
onClick={downloadUploadLogs}>
{constants.DOWNLOAD_UPLOAD_LOGS}
</div>
</div>
</DrawerSidebar>

View file

@ -16,6 +16,7 @@ export enum LS_KEYS {
LIVE_PHOTO_INFO_SHOWN_COUNT = 'livePhotoInfoShownCount',
FAILED_UPLOADS = 'failedUploads',
LOGS = 'logs',
USER_DETAILS = 'userDetails',
}
export const setData = (key: LS_KEYS, value: object) => {

View file

@ -718,6 +718,7 @@ const englishConstants = {
ALL_ALBUMS: 'All Albums',
PHOTOS: 'Photos',
ENTE: 'ente',
ENDS: 'Ends',
};
export default englishConstants;

View file

@ -15,6 +15,15 @@ export function dateStringWithMMH(unixTimeInMicroSeconds: number): string {
});
}
export function formatDateShort(date: number | Date) {
const dateTimeFormat = new Intl.DateTimeFormat('en-IN', {
year: '2-digit',
month: 'short',
day: 'numeric',
});
return dateTimeFormat.format(date);
}
export function getUnixTimeInMicroSecondsWithDelta(delta: TimeDelta): number {
let currentDate = new Date();
if (delta?.hours) {

View file

@ -1,3 +1,4 @@
import { UserDetails } from 'types/user';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
export function makeID(length) {
@ -21,3 +22,7 @@ export function getUserAnonymizedID() {
}
return anonymizeUserID;
}
export function getLocalUserDetails(): UserDetails {
return getData(LS_KEYS.USER_DETAILS);
}