add subscription component
This commit is contained in:
parent
9e01516f5d
commit
03228664f6
|
@ -1,14 +1,13 @@
|
||||||
import { ClickIndicator } from './clickIndicator';
|
import { ClickIndicator } from './clickIndicator';
|
||||||
import { IndividualUsageSection } from './individualUsageSection';
|
import { IndividualUsageSection } from './individualUsageSection';
|
||||||
import React, { useContext } from 'react';
|
import React, { useContext, useMemo } from 'react';
|
||||||
import { Box, CircularProgress } from '@mui/material';
|
import { Box } from '@mui/material';
|
||||||
import Container from 'components/Container';
|
|
||||||
import { UserDetails } from 'types/user';
|
import { UserDetails } from 'types/user';
|
||||||
|
|
||||||
import StorageSection from './storageSection';
|
import StorageSection from './storageSection';
|
||||||
import { GalleryContext } from 'pages/gallery';
|
import { GalleryContext } from 'pages/gallery';
|
||||||
import { FamilyUsageSection } from './familyUsageSection';
|
import { FamilyUsageSection } from './familyUsageSection';
|
||||||
import { hasNonAdminFamilyMembers } from 'utils/billing';
|
import { hasNonAdminFamilyMembers, isPartOfFamily } from 'utils/billing';
|
||||||
interface Iprops {
|
interface Iprops {
|
||||||
userDetails: UserDetails;
|
userDetails: UserDetails;
|
||||||
closeSidebar: () => void;
|
closeSidebar: () => void;
|
||||||
|
@ -17,6 +16,29 @@ interface Iprops {
|
||||||
export default function SubscriptionCard({ userDetails }: Iprops) {
|
export default function SubscriptionCard({ userDetails }: Iprops) {
|
||||||
const { showPlanSelectorModal } = useContext(GalleryContext);
|
const { showPlanSelectorModal } = useContext(GalleryContext);
|
||||||
|
|
||||||
|
if (!userDetails) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalUsage = useMemo(() => {
|
||||||
|
if (isPartOfFamily(userDetails.familyData)) {
|
||||||
|
return userDetails.familyData.members.reduce(
|
||||||
|
(sum, currentMember) => sum + currentMember.usage,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return userDetails.usage;
|
||||||
|
}
|
||||||
|
}, [userDetails]);
|
||||||
|
|
||||||
|
const totalStorage = useMemo(() => {
|
||||||
|
if (isPartOfFamily(userDetails.familyData)) {
|
||||||
|
return userDetails.familyData.storage;
|
||||||
|
} else {
|
||||||
|
return userDetails.subscription.storage;
|
||||||
|
}
|
||||||
|
}, [userDetails]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
display="flex"
|
display="flex"
|
||||||
|
@ -27,13 +49,11 @@ export default function SubscriptionCard({ userDetails }: Iprops) {
|
||||||
style={{ position: 'absolute' }}
|
style={{ position: 'absolute' }}
|
||||||
src="/subscription-card-background.png"
|
src="/subscription-card-background.png"
|
||||||
/>
|
/>
|
||||||
{!userDetails ? (
|
|
||||||
<Container>
|
|
||||||
<CircularProgress />
|
|
||||||
</Container>
|
|
||||||
) : (
|
|
||||||
<Box zIndex={1} position={'relative'} height={148}>
|
<Box zIndex={1} position={'relative'} height={148}>
|
||||||
<StorageSection userDetails={userDetails} />
|
<StorageSection
|
||||||
|
totalStorage={totalStorage}
|
||||||
|
totalUsage={totalUsage}
|
||||||
|
/>
|
||||||
{hasNonAdminFamilyMembers(userDetails.familyData) ? (
|
{hasNonAdminFamilyMembers(userDetails.familyData) ? (
|
||||||
<FamilyUsageSection userDetails={userDetails} />
|
<FamilyUsageSection userDetails={userDetails} />
|
||||||
) : (
|
) : (
|
||||||
|
@ -41,7 +61,6 @@ export default function SubscriptionCard({ userDetails }: Iprops) {
|
||||||
)}
|
)}
|
||||||
<ClickIndicator />
|
<ClickIndicator />
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,11 @@ import React from 'react';
|
||||||
import { convertBytesToHumanReadable } from 'utils/billing';
|
import { convertBytesToHumanReadable } from 'utils/billing';
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
|
|
||||||
export default function StorageSection({ userDetails }) {
|
interface Iprops {
|
||||||
|
totalUsage: number;
|
||||||
|
totalStorage: number;
|
||||||
|
}
|
||||||
|
export default function StorageSection({ totalUsage, totalStorage }: Iprops) {
|
||||||
return (
|
return (
|
||||||
<Box padding={2}>
|
<Box padding={2}>
|
||||||
<Typography variant="body2" color={'text.secondary'}>
|
<Typography variant="body2" color={'text.secondary'}>
|
||||||
|
@ -16,12 +20,12 @@ export default function StorageSection({ userDetails }) {
|
||||||
fontSize: '24px',
|
fontSize: '24px',
|
||||||
}}>
|
}}>
|
||||||
{`${convertBytesToHumanReadable(
|
{`${convertBytesToHumanReadable(
|
||||||
userDetails.usage,
|
totalStorage - totalUsage,
|
||||||
1
|
1
|
||||||
)} of ${convertBytesToHumanReadable(
|
)} ${constants.OF} ${convertBytesToHumanReadable(
|
||||||
userDetails.subscription.storage,
|
totalStorage,
|
||||||
0
|
0
|
||||||
)}`}
|
)} ${constants.FREE}`}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
export default function SubscriptionStatus() {
|
|
||||||
return <div>SubscriptionStatus</div>;
|
|
||||||
|
|
||||||
{
|
|
||||||
// const { setDialogMessage } = useContext(AppContext);
|
|
||||||
// async function onLeaveFamilyClick() {
|
|
||||||
// try {
|
|
||||||
// await billingService.leaveFamily();
|
|
||||||
// closeSidebar();
|
|
||||||
// } catch (e) {
|
|
||||||
// setDialogMessage({
|
|
||||||
// title: constants.ERROR,
|
|
||||||
// close: { variant: 'danger' },
|
|
||||||
// content: constants.UNKNOWN_ERROR,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
/* {!hasNonAdminFamilyMembers(userDetails.familyData) ||
|
|
||||||
isFamilyAdmin(userDetails.familyData) ? (
|
|
||||||
<div style={{ color: '#959595' }}>
|
|
||||||
{isSubscriptionActive(userDetails.subscription) ? (
|
|
||||||
isOnFreePlan(userDetails.subscription) ? (
|
|
||||||
constants.FREE_SUBSCRIPTION_INFO(
|
|
||||||
userDetails.subscription?.expiryTime
|
|
||||||
)
|
|
||||||
) : isSubscriptionCancelled(
|
|
||||||
userDetails.subscription
|
|
||||||
) ? (
|
|
||||||
constants.RENEWAL_CANCELLED_SUBSCRIPTION_INFO(
|
|
||||||
userDetails.subscription?.expiryTime
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
constants.RENEWAL_ACTIVE_SUBSCRIPTION_INFO(
|
|
||||||
userDetails.subscription?.expiryTime
|
|
||||||
)
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<p>{constants.SUBSCRIPTION_EXPIRED(onManageClick)}</p>
|
|
||||||
)}
|
|
||||||
<Button onClick={onManageClick}>
|
|
||||||
{isSubscribed(userDetails.subscription)
|
|
||||||
? constants.MANAGE
|
|
||||||
: constants.SUBSCRIBE}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div style={{ color: '#959595' }}>
|
|
||||||
{constants.FAMILY_PLAN_MANAGE_ADMIN_ONLY(
|
|
||||||
getFamilyPlanAdmin(userDetails.familyData)?.email
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
onClick={() =>
|
|
||||||
setDialogMessage({
|
|
||||||
title: `${constants.LEAVE_FAMILY}`,
|
|
||||||
content: constants.LEAVE_FAMILY_CONFIRM,
|
|
||||||
staticBackdrop: true,
|
|
||||||
proceed: {
|
|
||||||
text: constants.LEAVE_FAMILY,
|
|
||||||
action: onLeaveFamilyClick,
|
|
||||||
variant: 'danger',
|
|
||||||
},
|
|
||||||
close: { text: constants.CANCEL },
|
|
||||||
})
|
|
||||||
}>
|
|
||||||
{constants.LEAVE_FAMILY}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasNonAdminFamilyMembers(userDetails.familyData)
|
|
||||||
? constants.FAMILY_USAGE_INFO(
|
|
||||||
userDetails.usage,
|
|
||||||
convertBytesToHumanReadable(
|
|
||||||
getStorage(userDetails.familyData)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: constants.USAGE_INFO(
|
|
||||||
userDetails.usage,
|
|
||||||
convertBytesToHumanReadable(
|
|
||||||
userDetails.subscription?.storage
|
|
||||||
)
|
|
||||||
)} */
|
|
||||||
}
|
|
||||||
}
|
|
31
src/components/Sidebar/SubscriptionStatus/admin.tsx
Normal file
31
src/components/Sidebar/SubscriptionStatus/admin.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { Typography } from '@mui/material';
|
||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
isSubscriptionActive,
|
||||||
|
isOnFreePlan,
|
||||||
|
isSubscriptionCancelled,
|
||||||
|
} from 'utils/billing';
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
|
||||||
|
export function AdminSubscriptionStatus({
|
||||||
|
userDetails,
|
||||||
|
showPlanSelectorModal,
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color={'text.secondary'}
|
||||||
|
onClick={showPlanSelectorModal}>
|
||||||
|
{isSubscriptionActive(userDetails.subscription)
|
||||||
|
? isOnFreePlan(userDetails.subscription)
|
||||||
|
? constants.FREE_SUBSCRIPTION_INFO(
|
||||||
|
userDetails.subscription?.expiryTime
|
||||||
|
)
|
||||||
|
: isSubscriptionCancelled(userDetails.subscription) &&
|
||||||
|
constants.RENEWAL_CANCELLED_SUBSCRIPTION_INFO(
|
||||||
|
userDetails.subscription?.expiryTime
|
||||||
|
)
|
||||||
|
: constants.SUBSCRIPTION_EXPIRED_MESSAGE(showPlanSelectorModal)}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
}
|
27
src/components/Sidebar/SubscriptionStatus/index.tsx
Normal file
27
src/components/Sidebar/SubscriptionStatus/index.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { MemberSubscriptionStatus } from './member';
|
||||||
|
import { AdminSubscriptionStatus as AdminSubscriptionStatus } from './admin';
|
||||||
|
import { GalleryContext } from 'pages/gallery';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import { hasNonAdminFamilyMembers, isFamilyAdmin } from 'utils/billing';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
|
||||||
|
export default function SubscriptionStatus({ userDetails }) {
|
||||||
|
const { showPlanSelectorModal } = useContext(GalleryContext);
|
||||||
|
|
||||||
|
if (!userDetails) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Box px={1}>
|
||||||
|
{!hasNonAdminFamilyMembers(userDetails.familyData) ||
|
||||||
|
isFamilyAdmin(userDetails.familyData) ? (
|
||||||
|
<AdminSubscriptionStatus
|
||||||
|
userDetails={userDetails}
|
||||||
|
showPlanSelectorModal={showPlanSelectorModal}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<MemberSubscriptionStatus userDetails={userDetails} />
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
46
src/components/Sidebar/SubscriptionStatus/member.tsx
Normal file
46
src/components/Sidebar/SubscriptionStatus/member.tsx
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import { Button, Stack, Typography } from '@mui/material';
|
||||||
|
import { AppContext } from 'pages/_app';
|
||||||
|
import React, { useContext } from 'react';
|
||||||
|
import billingService from 'services/billingService';
|
||||||
|
import { getFamilyPlanAdmin } from 'utils/billing';
|
||||||
|
import constants from 'utils/strings/constants';
|
||||||
|
export function MemberSubscriptionStatus({ userDetails }) {
|
||||||
|
const { setDialogMessage } = useContext(AppContext);
|
||||||
|
|
||||||
|
async function onLeaveFamilyClick() {
|
||||||
|
try {
|
||||||
|
await billingService.leaveFamily();
|
||||||
|
} catch (e) {
|
||||||
|
setDialogMessage({
|
||||||
|
title: constants.ERROR,
|
||||||
|
close: { variant: 'danger' },
|
||||||
|
content: constants.UNKNOWN_ERROR,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const confirmLeaveFamily = () =>
|
||||||
|
setDialogMessage({
|
||||||
|
title: `${constants.LEAVE_FAMILY}`,
|
||||||
|
content: constants.LEAVE_FAMILY_CONFIRM,
|
||||||
|
proceed: {
|
||||||
|
text: constants.LEAVE_FAMILY,
|
||||||
|
action: onLeaveFamilyClick,
|
||||||
|
variant: 'danger',
|
||||||
|
},
|
||||||
|
close: {
|
||||||
|
text: constants.CANCEL,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<Stack spacing={1}>
|
||||||
|
<Typography variant="body2" color="text.secondary ">
|
||||||
|
{constants.FAMILY_PLAN_MANAGE_ADMIN_ONLY(
|
||||||
|
getFamilyPlanAdmin(userDetails.familyData)?.email
|
||||||
|
)}
|
||||||
|
</Typography>
|
||||||
|
<Button onClick={confirmLeaveFamily}>
|
||||||
|
{constants.LEAVE_FAMILY}
|
||||||
|
</Button>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,20 +1,17 @@
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { SpaceBetweenFlex } from 'components/Container';
|
|
||||||
import { PaddedDivider } from './styledComponents';
|
|
||||||
import SubscriptionCard from './SubscriptionCard';
|
import SubscriptionCard from './SubscriptionCard';
|
||||||
import { getUserDetailsV2 } from 'services/userService';
|
import { getUserDetailsV2 } from 'services/userService';
|
||||||
import { UserDetails } from 'types/user';
|
import { UserDetails } from 'types/user';
|
||||||
import { LS_KEYS } from 'utils/storage/localStorage';
|
import { LS_KEYS } from 'utils/storage/localStorage';
|
||||||
import { useLocalState } from 'hooks/useLocalState';
|
import { useLocalState } from 'hooks/useLocalState';
|
||||||
import { THEMES } from 'types/theme';
|
|
||||||
import ThemeSwitcher from './ThemeSwitcher';
|
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
|
import SubscriptionStatus from './SubscriptionStatus';
|
||||||
|
import Stack from '@mui/material/Stack';
|
||||||
|
|
||||||
export default function UserDetailsSection({ sidebarView, closeSidebar }) {
|
export default function UserDetailsSection({ sidebarView, closeSidebar }) {
|
||||||
const [userDetails, setUserDetails] = useLocalState<UserDetails>(
|
const [userDetails, setUserDetails] = useLocalState<UserDetails>(
|
||||||
LS_KEYS.USER_DETAILS
|
LS_KEYS.USER_DETAILS
|
||||||
);
|
);
|
||||||
const [theme, setTheme] = useLocalState<THEMES>(LS_KEYS.THEME, THEMES.DARK);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sidebarView) {
|
if (!sidebarView) {
|
||||||
|
@ -28,16 +25,13 @@ export default function UserDetailsSection({ sidebarView, closeSidebar }) {
|
||||||
}, [sidebarView]);
|
}, [sidebarView]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Stack spacing={1}>
|
||||||
<SpaceBetweenFlex px={1}>
|
|
||||||
<Typography>{userDetails?.email}</Typography>
|
<Typography>{userDetails?.email}</Typography>
|
||||||
<ThemeSwitcher theme={theme} setTheme={setTheme} />
|
|
||||||
</SpaceBetweenFlex>
|
|
||||||
<PaddedDivider invisible />
|
|
||||||
<SubscriptionCard
|
<SubscriptionCard
|
||||||
userDetails={userDetails}
|
userDetails={userDetails}
|
||||||
closeSidebar={closeSidebar}
|
closeSidebar={closeSidebar}
|
||||||
/>
|
/>
|
||||||
</>
|
<SubscriptionStatus userDetails={userDetails} />
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Box, Typography } from '@mui/material';
|
import { Box, Typography } from '@mui/material';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
|
import LinkButton from 'components/pages/gallery/LinkButton';
|
||||||
import { DotSeparator } from 'components/Sidebar/styledComponents';
|
import { DotSeparator } from 'components/Sidebar/styledComponents';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
|
@ -126,6 +127,12 @@ const englishConstants = {
|
||||||
UPLOADING_FILES: 'File upload',
|
UPLOADING_FILES: 'File upload',
|
||||||
FILE_NOT_UPLOADED_LIST: 'The following files were not uploaded',
|
FILE_NOT_UPLOADED_LIST: 'The following files were not uploaded',
|
||||||
SUBSCRIPTION_EXPIRED: 'Subscription expired',
|
SUBSCRIPTION_EXPIRED: 'Subscription expired',
|
||||||
|
SUBSCRIPTION_EXPIRED_MESSAGE: (onClick) => (
|
||||||
|
<>
|
||||||
|
Your subscription has expired, please{' '}
|
||||||
|
<LinkButton onClick={onClick}> renew </LinkButton>
|
||||||
|
</>
|
||||||
|
),
|
||||||
STORAGE_QUOTA_EXCEEDED: 'Storage limit exceeded',
|
STORAGE_QUOTA_EXCEEDED: 'Storage limit exceeded',
|
||||||
INITIAL_LOAD_DELAY_WARNING: 'First load may take some time',
|
INITIAL_LOAD_DELAY_WARNING: 'First load may take some time',
|
||||||
USER_DOES_NOT_EXIST: 'Sorry, could not find a user with that email',
|
USER_DOES_NOT_EXIST: 'Sorry, could not find a user with that email',
|
||||||
|
@ -274,10 +281,8 @@ const englishConstants = {
|
||||||
|
|
||||||
FAMILY_PLAN_MANAGE_ADMIN_ONLY: (adminEmail) => (
|
FAMILY_PLAN_MANAGE_ADMIN_ONLY: (adminEmail) => (
|
||||||
<>
|
<>
|
||||||
<p>
|
Only your family plan admin <strong>{adminEmail}</strong> can change
|
||||||
Only your family plan admin <strong>{adminEmail}</strong> can
|
the plan
|
||||||
change the plan
|
|
||||||
</p>
|
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
RENEWAL_ACTIVE_SUBSCRIPTION_INFO: (expiryTime) => (
|
RENEWAL_ACTIVE_SUBSCRIPTION_INFO: (expiryTime) => (
|
||||||
|
@ -777,6 +782,8 @@ const englishConstants = {
|
||||||
USED: 'used',
|
USED: 'used',
|
||||||
YOU: 'You',
|
YOU: 'You',
|
||||||
FAMILY: 'Family',
|
FAMILY: 'Family',
|
||||||
|
FREE: 'free',
|
||||||
|
OF: 'of',
|
||||||
};
|
};
|
||||||
|
|
||||||
export default englishConstants;
|
export default englishConstants;
|
||||||
|
|
Loading…
Reference in a new issue