Merge pull request #608 from ente-io/family-member-screen
Family member screen
This commit is contained in:
commit
ae3f64745d
BIN
public/images/family_plan_leave@3x.png
Normal file
BIN
public/images/family_plan_leave@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 408 KiB |
73
src/components/MemberSubscriptionManage.tsx
Normal file
73
src/components/MemberSubscriptionManage.tsx
Normal file
|
@ -0,0 +1,73 @@
|
|||
import { Button, DialogContent, Typography } from '@mui/material';
|
||||
import VerticallyCentered from 'components/Container';
|
||||
import DialogBoxBase from 'components/DialogBox/base';
|
||||
import DialogTitleWithCloseButton from 'components/DialogBox/titleWithCloseButton';
|
||||
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 MemberSubscriptionManage({ open, userDetails, onClose }) {
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
if (!userDetails) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
return (
|
||||
<DialogBoxBase open={open} onClose={onClose} maxWidth="xs">
|
||||
<DialogTitleWithCloseButton onClose={onClose}>
|
||||
<Typography variant="h3">{constants.SUBSCRIPTION}</Typography>
|
||||
<Typography color={'text.secondary'}>
|
||||
{constants.FAMILY_PLAN}
|
||||
</Typography>
|
||||
</DialogTitleWithCloseButton>
|
||||
<DialogContent>
|
||||
<VerticallyCentered>
|
||||
<Typography color="text.secondary ">
|
||||
{constants.FAMILY_SUBSCRIPTION_INFO(
|
||||
getFamilyPlanAdmin(userDetails.familyData)?.email
|
||||
)}
|
||||
</Typography>
|
||||
<img
|
||||
height="267px"
|
||||
width="256px"
|
||||
src="/images/family_plan_leave@3x.png"
|
||||
/>
|
||||
<Button
|
||||
size="large"
|
||||
variant="outlined"
|
||||
color="danger"
|
||||
onClick={confirmLeaveFamily}>
|
||||
{constants.LEAVE_FAMILY}
|
||||
</Button>
|
||||
</VerticallyCentered>
|
||||
</DialogContent>
|
||||
</DialogBoxBase>
|
||||
);
|
||||
}
|
|
@ -12,10 +12,13 @@ import {
|
|||
import { SubscriptionCardContentOverlay } from './contentOverlay';
|
||||
interface Iprops {
|
||||
userDetails: UserDetails;
|
||||
closeSidebar: () => void;
|
||||
openMemberSubscriptionDialog: () => void;
|
||||
}
|
||||
|
||||
export default function SubscriptionCard({ userDetails }: Iprops) {
|
||||
export default function SubscriptionCard({
|
||||
userDetails,
|
||||
openMemberSubscriptionDialog,
|
||||
}: Iprops) {
|
||||
const { showPlanSelectorModal } = useContext(GalleryContext);
|
||||
|
||||
if (!userDetails) {
|
||||
|
@ -30,9 +33,9 @@ export default function SubscriptionCard({ userDetails }: Iprops) {
|
|||
);
|
||||
}
|
||||
|
||||
const allowClick =
|
||||
!isPartOfFamily(userDetails.familyData) ||
|
||||
isFamilyAdmin(userDetails.familyData);
|
||||
const isMemberSubscription =
|
||||
isPartOfFamily(userDetails.familyData) &&
|
||||
!isFamilyAdmin(userDetails.familyData);
|
||||
|
||||
return (
|
||||
<Box position="relative">
|
||||
|
@ -43,7 +46,13 @@ export default function SubscriptionCard({ userDetails }: Iprops) {
|
|||
}}
|
||||
src="/images/subscription-card-background.png"
|
||||
/>
|
||||
{allowClick && <ClickOverlay onClick={showPlanSelectorModal} />}
|
||||
<ClickOverlay
|
||||
onClick={
|
||||
isMemberSubscription
|
||||
? openMemberSubscriptionDialog
|
||||
: showPlanSelectorModal
|
||||
}
|
||||
/>
|
||||
<SubscriptionCardContentOverlay
|
||||
hasNonAdminFamilyMembers={hasNonAdminFamilyMembers}
|
||||
userDetails={userDetails}
|
||||
|
|
|
@ -15,7 +15,8 @@ export function AdminSubscriptionStatus({
|
|||
<Typography
|
||||
variant="body2"
|
||||
color={'text.secondary'}
|
||||
onClick={showPlanSelectorModal}>
|
||||
onClick={showPlanSelectorModal}
|
||||
sx={{ cursor: 'pointer' }}>
|
||||
{isSubscriptionActive(userDetails.subscription)
|
||||
? isOnFreePlan(userDetails.subscription)
|
||||
? constants.FREE_SUBSCRIPTION_INFO(
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { MemberSubscriptionStatus } from './member';
|
||||
import { AdminSubscriptionStatus as AdminSubscriptionStatus } from './admin';
|
||||
import { GalleryContext } from 'pages/gallery';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import {
|
||||
hasNonAdminFamilyMembers,
|
||||
hasPaidSubscription,
|
||||
isFamilyAdmin,
|
||||
isOnFreePlan,
|
||||
isSubscriptionActive,
|
||||
|
@ -11,6 +10,8 @@ import {
|
|||
} from 'utils/billing';
|
||||
import Box from '@mui/material/Box';
|
||||
import { UserDetails } from 'types/user';
|
||||
import constants from 'utils/strings/constants';
|
||||
import { Typography } from '@mui/material';
|
||||
|
||||
export default function SubscriptionStatus({
|
||||
userDetails,
|
||||
|
@ -34,14 +35,29 @@ export default function SubscriptionStatus({
|
|||
|
||||
return (
|
||||
<Box px={1}>
|
||||
{!hasNonAdminFamilyMembers(userDetails.familyData) ||
|
||||
isFamilyAdmin(userDetails.familyData) ? (
|
||||
<AdminSubscriptionStatus
|
||||
userDetails={userDetails}
|
||||
showPlanSelectorModal={showPlanSelectorModal}
|
||||
/>
|
||||
) : (
|
||||
<MemberSubscriptionStatus userDetails={userDetails} />
|
||||
{(!hasNonAdminFamilyMembers(userDetails.familyData) ||
|
||||
isFamilyAdmin(userDetails.familyData) ||
|
||||
hasPaidSubscription(userDetails.subscription)) && (
|
||||
<Typography
|
||||
variant="body2"
|
||||
color={'text.secondary'}
|
||||
onClick={showPlanSelectorModal}
|
||||
sx={{ cursor: 'pointer' }}>
|
||||
{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>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
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>
|
||||
);
|
||||
}
|
|
@ -24,10 +24,7 @@ export default function Sidebar({
|
|||
<DrawerSidebar open={sidebarView} onClose={closeSidebar}>
|
||||
<HeaderSection closeSidebar={closeSidebar} />
|
||||
<PaddedDivider spaced />
|
||||
<UserDetailsSection
|
||||
sidebarView={sidebarView}
|
||||
closeSidebar={closeSidebar}
|
||||
/>
|
||||
<UserDetailsSection sidebarView={sidebarView} />
|
||||
<PaddedDivider invisible />
|
||||
<ShortcutSection
|
||||
closeSidebar={closeSidebar}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import SubscriptionCard from './SubscriptionCard';
|
||||
import { getUserDetailsV2 } from 'services/userService';
|
||||
import { UserDetails } from 'types/user';
|
||||
|
@ -8,12 +8,21 @@ import Typography from '@mui/material/Typography';
|
|||
import SubscriptionStatus from './SubscriptionStatus';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import { Skeleton } from '@mui/material';
|
||||
import { MemberSubscriptionManage } from '../MemberSubscriptionManage';
|
||||
|
||||
export default function UserDetailsSection({ sidebarView, closeSidebar }) {
|
||||
export default function UserDetailsSection({ sidebarView }) {
|
||||
const [userDetails, setUserDetails] = useLocalState<UserDetails>(
|
||||
LS_KEYS.USER_DETAILS
|
||||
);
|
||||
|
||||
const [memberSubscriptionManageView, setMemberSubscriptionManageView] =
|
||||
useState(false);
|
||||
|
||||
const openMemberSubscriptionManage = () =>
|
||||
setMemberSubscriptionManageView(true);
|
||||
const closeMemberSubscriptionManage = () =>
|
||||
setMemberSubscriptionManageView(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!sidebarView) {
|
||||
return;
|
||||
|
@ -26,20 +35,28 @@ export default function UserDetailsSection({ sidebarView, closeSidebar }) {
|
|||
}, [sidebarView]);
|
||||
|
||||
return (
|
||||
<Stack spacing={1}>
|
||||
<Typography px={1} color="text.secondary">
|
||||
{userDetails ? (
|
||||
userDetails.email
|
||||
) : (
|
||||
<Skeleton animation="wave" />
|
||||
)}
|
||||
</Typography>
|
||||
<>
|
||||
<Stack spacing={1}>
|
||||
<Typography px={1} color="text.secondary">
|
||||
{userDetails ? (
|
||||
userDetails.email
|
||||
) : (
|
||||
<Skeleton animation="wave" />
|
||||
)}
|
||||
</Typography>
|
||||
|
||||
<SubscriptionCard
|
||||
<SubscriptionCard
|
||||
userDetails={userDetails}
|
||||
openMemberSubscriptionDialog={openMemberSubscriptionManage}
|
||||
/>
|
||||
<SubscriptionStatus userDetails={userDetails} />
|
||||
</Stack>
|
||||
|
||||
<MemberSubscriptionManage
|
||||
userDetails={userDetails}
|
||||
closeSidebar={closeSidebar}
|
||||
open={memberSubscriptionManageView}
|
||||
onClose={closeMemberSubscriptionManage}
|
||||
/>
|
||||
<SubscriptionStatus userDetails={userDetails} />
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -84,7 +84,6 @@ const darkThemeOptions = createTheme({
|
|||
},
|
||||
styleOverrides: {
|
||||
root: {
|
||||
textTransform: 'none',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '4px',
|
||||
},
|
||||
|
@ -171,9 +170,27 @@ const darkThemeOptions = createTheme({
|
|||
display: 'block',
|
||||
},
|
||||
caption: {
|
||||
display: 'block',
|
||||
fontSize: '12px',
|
||||
lineHeight: '15px',
|
||||
},
|
||||
h1: {
|
||||
fontSize: '36px',
|
||||
lineHeight: '44px',
|
||||
},
|
||||
h2: {
|
||||
fontSize: '30px',
|
||||
lineHeight: '36px',
|
||||
},
|
||||
h3: {
|
||||
fontSize: '24px',
|
||||
lineHeight: '29px',
|
||||
},
|
||||
h4: {
|
||||
fontSize: '18px',
|
||||
lineHeight: '22px',
|
||||
},
|
||||
|
||||
fontFamily: ['Inter', 'sans-serif'].join(','),
|
||||
},
|
||||
});
|
||||
|
|
|
@ -252,6 +252,7 @@ const englishConstants = {
|
|||
// ========================
|
||||
// Subscription
|
||||
// ========================
|
||||
SUBSCRIPTION: 'Subscription',
|
||||
SUBSCRIBE: 'Subscribe',
|
||||
SUBSCRIPTION_PLAN: 'Subscription plan',
|
||||
USAGE_DETAILS: 'Usage',
|
||||
|
@ -273,10 +274,9 @@ const englishConstants = {
|
|||
</>
|
||||
),
|
||||
|
||||
FAMILY_PLAN_MANAGE_ADMIN_ONLY: (adminEmail) => (
|
||||
FAMILY_SUBSCRIPTION_INFO: (adminEmail) => (
|
||||
<>
|
||||
Only your family plan admin <strong>{adminEmail}</strong> can change
|
||||
the plan
|
||||
You are on a family plan managed by <strong>{adminEmail}</strong>
|
||||
</>
|
||||
),
|
||||
RENEWAL_ACTIVE_SUBSCRIPTION_INFO: (expiryTime) => (
|
||||
|
@ -777,6 +777,7 @@ const englishConstants = {
|
|||
OF: 'of',
|
||||
MONTH_SHORT: 'mo',
|
||||
YEAR_SHORT: 'yr',
|
||||
FAMILY_PLAN: 'Family plan',
|
||||
};
|
||||
|
||||
export default englishConstants;
|
||||
|
|
Loading…
Reference in a new issue