Tiers make sense for admins now
This commit is contained in:
parent
d8032e1c9e
commit
3aba7404fc
18 changed files with 457 additions and 225 deletions
|
@ -24,6 +24,10 @@ import accountApi, {UnauthorizedError} from "../app/AccountApi";
|
|||
import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined';
|
||||
import {Pref, PrefGroup} from "./Pref";
|
||||
import db from "../app/db";
|
||||
import i18n from "i18next";
|
||||
import humanizeDuration from "humanize-duration";
|
||||
import UpgradeDialog from "./UpgradeDialog";
|
||||
import CelebrationIcon from "@mui/icons-material/Celebration";
|
||||
|
||||
const Account = () => {
|
||||
if (!session.exists()) {
|
||||
|
@ -166,10 +170,12 @@ const ChangePasswordDialog = (props) => {
|
|||
const Stats = () => {
|
||||
const { t } = useTranslation();
|
||||
const { account } = useOutletContext();
|
||||
const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false);
|
||||
|
||||
if (!account) {
|
||||
return <></>;
|
||||
}
|
||||
const tierCode = account.tier.code ?? "none";
|
||||
|
||||
const normalize = (value, max) => Math.min(value / max * 100, 100);
|
||||
const barColor = (remaining, limit) => {
|
||||
if (account.role === "admin") {
|
||||
|
@ -188,34 +194,63 @@ const Stats = () => {
|
|||
<PrefGroup>
|
||||
<Pref title={t("account_usage_tier_title")}>
|
||||
<div>
|
||||
{account.role === "admin"
|
||||
? <>{t("account_usage_unlimited")} <Tooltip title={t("account_basics_username_admin_tooltip")}><span style={{cursor: "default"}}>👑</span></Tooltip></>
|
||||
: t(`account_usage_tier_code_${tierCode}`)}
|
||||
{config.enable_payments && account.tier.upgradeable &&
|
||||
<em>{" "}
|
||||
<Link onClick={() => {}}>Upgrade</Link>
|
||||
</em>
|
||||
{account.role === "admin" &&
|
||||
<>
|
||||
{t("account_usage_tier_admin")}
|
||||
{" "}{account.tier ? `(with ${account.tier.name} tier)` : `(no tier)`}
|
||||
</>
|
||||
}
|
||||
{account.role === "user" && account.tier &&
|
||||
<>{account.tier.name}</>
|
||||
}
|
||||
{account.role === "user" && !account.tier &&
|
||||
t("account_usage_tier_none")
|
||||
}
|
||||
{config.enable_payments && account.role === "user" && (!account.tier || !account.tier.paid) &&
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
startIcon={<CelebrationIcon sx={{ color: "#55b86e" }}/>}
|
||||
onClick={() => setUpgradeDialogOpen(true)}
|
||||
sx={{ml: 1}}
|
||||
>{t("account_usage_tier_upgrade_button")}</Button>
|
||||
}
|
||||
{config.enable_payments && account.role === "user" && account.tier?.paid &&
|
||||
<Button
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onClick={() => setUpgradeDialogOpen(true)}
|
||||
sx={{ml: 1}}
|
||||
>{t("account_usage_tier_change_button")}</Button>
|
||||
}
|
||||
<UpgradeDialog
|
||||
open={upgradeDialogOpen}
|
||||
onCancel={() => setUpgradeDialogOpen(false)}
|
||||
/>
|
||||
</div>
|
||||
</Pref>
|
||||
<Pref title={t("account_usage_topics_title")}>
|
||||
{account.limits.reservations > 0 &&
|
||||
<>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{account.stats.reservations}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.role === "user" ? t("account_usage_of_limit", { limit: account.limits.reservations }) : t("account_usage_unlimited")}</Typography>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={account.limits.reservations > 0 ? normalize(account.stats.reservations, account.limits.reservations) : 100}
|
||||
color={barColor(account.stats.reservations_remaining, account.limits.reservations)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{account.limits.reservations === 0 &&
|
||||
<em>No reserved topics for this account</em>
|
||||
}
|
||||
</Pref>
|
||||
{account.role !== "admin" &&
|
||||
<Pref title={t("account_usage_reservations_title")}>
|
||||
{account.limits.reservations > 0 &&
|
||||
<>
|
||||
<div>
|
||||
<Typography variant="body2"
|
||||
sx={{float: "left"}}>{account.stats.reservations}</Typography>
|
||||
<Typography variant="body2"
|
||||
sx={{float: "right"}}>{account.role === "user" ? t("account_usage_of_limit", {limit: account.limits.reservations}) : t("account_usage_unlimited")}</Typography>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={account.limits.reservations > 0 ? normalize(account.stats.reservations, account.limits.reservations) : 100}
|
||||
color={barColor(account.stats.reservations_remaining, account.limits.reservations)}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
{account.limits.reservations === 0 &&
|
||||
<em>No reserved topics for this account</em>
|
||||
}
|
||||
</Pref>
|
||||
}
|
||||
<Pref title={
|
||||
<>
|
||||
{t("account_usage_messages_title")}
|
||||
|
@ -224,11 +259,11 @@ const Stats = () => {
|
|||
}>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{account.stats.messages}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.limits.messages > 0 ? t("account_usage_of_limit", { limit: account.limits.messages }) : t("account_usage_unlimited")}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.role === "user" ? t("account_usage_of_limit", { limit: account.limits.messages }) : t("account_usage_unlimited")}</Typography>
|
||||
</div>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={account.limits.messages > 0 ? normalize(account.stats.messages, account.limits.messages) : 100}
|
||||
value={account.role === "user" ? normalize(account.stats.messages, account.limits.messages) : 100}
|
||||
color={account.role === "user" && account.stats.messages_remaining === 0 ? 'error' : 'primary'}
|
||||
/>
|
||||
</Pref>
|
||||
|
@ -248,14 +283,17 @@ const Stats = () => {
|
|||
color={account?.role !== "admin" && account.stats.emails_remaining === 0 ? 'error' : 'primary'}
|
||||
/>
|
||||
</Pref>
|
||||
<Pref title={
|
||||
<>
|
||||
{t("account_usage_attachment_storage_title")}
|
||||
{account.role === "user" &&
|
||||
<Tooltip title={t("account_usage_attachment_storage_subtitle", { filesize: formatBytes(account.limits.attachment_file_size) })}><span><InfoIcon/></span></Tooltip>
|
||||
}
|
||||
</>
|
||||
}>
|
||||
<Pref
|
||||
alignTop
|
||||
title={t("account_usage_attachment_storage_title")}
|
||||
description={t("account_usage_attachment_storage_description", {
|
||||
filesize: formatBytes(account.limits.attachment_file_size),
|
||||
expiry: humanizeDuration(account.limits.attachment_expiry_duration * 1000, {
|
||||
language: i18n.language,
|
||||
fallbacks: ["en"]
|
||||
})
|
||||
})}
|
||||
>
|
||||
<div>
|
||||
<Typography variant="body2" sx={{float: "left"}}>{formatBytes(account.stats.attachment_total_size)}</Typography>
|
||||
<Typography variant="body2" sx={{float: "right"}}>{account.limits.attachment_total_size > 0 ? t("account_usage_of_limit", { limit: formatBytes(account.limits.attachment_total_size) }) : t("account_usage_unlimited")}</Typography>
|
||||
|
@ -269,7 +307,7 @@ const Stats = () => {
|
|||
</PrefGroup>
|
||||
{account.limits.basis === "ip" &&
|
||||
<Typography variant="body1">
|
||||
<em>{t("account_usage_basis_ip_description")}</em>
|
||||
{t("account_usage_basis_ip_description")}
|
||||
</Typography>
|
||||
}
|
||||
</Card>
|
||||
|
|
|
@ -29,6 +29,7 @@ import {Trans, useTranslation} from "react-i18next";
|
|||
import session from "../app/Session";
|
||||
import accountApi from "../app/AccountApi";
|
||||
import CelebrationIcon from '@mui/icons-material/Celebration';
|
||||
import UpgradeDialog from "./UpgradeDialog";
|
||||
|
||||
const navWidth = 280;
|
||||
|
||||
|
@ -99,7 +100,9 @@ const NavList = (props) => {
|
|||
navigate(routes.account);
|
||||
};
|
||||
|
||||
const showUpgradeBanner = config.enable_payments && (!props.account || props.account.tier.upgradeable);
|
||||
const isAdmin = props.account?.role === "admin";
|
||||
const isPaid = props.account?.tier?.paid;
|
||||
const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid;// && (!props.account || !props.account.tier || !props.account.tier.paid || props.account);
|
||||
const showSubscriptionsList = props.subscriptions?.length > 0;
|
||||
const showNotificationBrowserNotSupportedBox = !notifier.browserSupported();
|
||||
const showNotificationContextNotSupportedBox = notifier.browserSupported() && !notifier.contextSupported(); // Only show if notifications are generally supported in the browser
|
||||
|
@ -154,32 +157,7 @@ const NavList = (props) => {
|
|||
<ListItemText primary={t("nav_button_subscribe")}/>
|
||||
</ListItemButton>
|
||||
{showUpgradeBanner &&
|
||||
<Box sx={{
|
||||
position: "fixed",
|
||||
width: `${Navigation.width - 1}px`,
|
||||
bottom: 0,
|
||||
mt: 'auto',
|
||||
background: "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)",
|
||||
}}>
|
||||
<Divider/>
|
||||
<ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
|
||||
<ListItemIcon><CelebrationIcon sx={{ color: "#55b86e" }} fontSize="large"/></ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{ ml: 1 }}
|
||||
primary={"Upgrade to ntfy Pro"}
|
||||
secondary={"Reserve topics, more messages & emails, bigger attachments"}
|
||||
primaryTypographyProps={{
|
||||
style: {
|
||||
fontWeight: 500,
|
||||
background: "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
</Box>
|
||||
|
||||
<UpgradeBanner/>
|
||||
}
|
||||
</List>
|
||||
<SubscribeDialog
|
||||
|
@ -193,6 +171,41 @@ const NavList = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
const UpgradeBanner = () => {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
return (
|
||||
<Box sx={{
|
||||
position: "fixed",
|
||||
width: `${Navigation.width - 1}px`,
|
||||
bottom: 0,
|
||||
mt: 'auto',
|
||||
background: "linear-gradient(150deg, rgba(196, 228, 221, 0.46) 0%, rgb(255, 255, 255) 100%)",
|
||||
}}>
|
||||
<Divider/>
|
||||
<ListItemButton onClick={() => setDialogOpen(true)}>
|
||||
<ListItemIcon><CelebrationIcon sx={{ color: "#55b86e" }} fontSize="large"/></ListItemIcon>
|
||||
<ListItemText
|
||||
sx={{ ml: 1 }}
|
||||
primary={"Upgrade to ntfy Pro"}
|
||||
secondary={"Reserve topics, more messages & emails, bigger attachments"}
|
||||
primaryTypographyProps={{
|
||||
style: {
|
||||
fontWeight: 500,
|
||||
background: "-webkit-linear-gradient(45deg, #09009f, #00ff95 80%)",
|
||||
WebkitBackgroundClip: "text",
|
||||
WebkitTextFillColor: "transparent"
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</ListItemButton>
|
||||
<UpgradeDialog
|
||||
open={dialogOpen}
|
||||
onCancel={() => setDialogOpen(false)}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const SubscriptionList = (props) => {
|
||||
const sortedSubscriptions = props.subscriptions.sort( (a, b) => {
|
||||
return (topicUrl(a.baseUrl, a.topic) < topicUrl(b.baseUrl, b.topic)) ? -1 : 1;
|
||||
|
|
|
@ -9,6 +9,7 @@ export const PrefGroup = (props) => {
|
|||
};
|
||||
|
||||
export const Pref = (props) => {
|
||||
const justifyContent = (props.alignTop) ? "normal" : "center";
|
||||
return (
|
||||
<div
|
||||
role="row"
|
||||
|
@ -27,7 +28,7 @@ export const Pref = (props) => {
|
|||
flex: '1 0 40%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
justifyContent: justifyContent,
|
||||
paddingRight: '30px'
|
||||
}}
|
||||
>
|
||||
|
@ -40,7 +41,7 @@ export const Pref = (props) => {
|
|||
flex: '1 0 calc(60% - 50px)',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center'
|
||||
justifyContent: justifyContent
|
||||
}}
|
||||
>
|
||||
{props.children}
|
||||
|
|
44
web/src/components/UpgradeDialog.js
Normal file
44
web/src/components/UpgradeDialog.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import * as React from 'react';
|
||||
import {useState} from 'react';
|
||||
import Button from '@mui/material/Button';
|
||||
import TextField from '@mui/material/TextField';
|
||||
import Dialog from '@mui/material/Dialog';
|
||||
import DialogContent from '@mui/material/DialogContent';
|
||||
import DialogContentText from '@mui/material/DialogContentText';
|
||||
import DialogTitle from '@mui/material/DialogTitle';
|
||||
import {Autocomplete, Checkbox, FormControlLabel, FormGroup, useMediaQuery} from "@mui/material";
|
||||
import theme from "./theme";
|
||||
import api from "../app/Api";
|
||||
import {randomAlphanumericString, topicUrl, validTopic, validUrl} from "../app/utils";
|
||||
import userManager from "../app/UserManager";
|
||||
import subscriptionManager from "../app/SubscriptionManager";
|
||||
import poller from "../app/Poller";
|
||||
import DialogFooter from "./DialogFooter";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import session from "../app/Session";
|
||||
import routes from "./routes";
|
||||
import accountApi, {TopicReservedError, UnauthorizedError} from "../app/AccountApi";
|
||||
import ReserveTopicSelect from "./ReserveTopicSelect";
|
||||
import {useOutletContext} from "react-router-dom";
|
||||
|
||||
const UpgradeDialog = (props) => {
|
||||
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
|
||||
const handleSuccess = async () => {
|
||||
// TODO
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
|
||||
<DialogTitle>Upgrade to Pro</DialogTitle>
|
||||
<DialogContent>
|
||||
Content
|
||||
</DialogContent>
|
||||
<DialogFooter>
|
||||
Footer
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default UpgradeDialog;
|
Loading…
Add table
Add a link
Reference in a new issue