Payments webhook test, delete attachments/messages when reservations are removed,
This commit is contained in:
parent
45b97c7054
commit
31a3bb7cd6
16 changed files with 571 additions and 157 deletions
|
@ -201,15 +201,19 @@
|
|||
"account_delete_dialog_label": "Type '{{username}}' to delete account",
|
||||
"account_delete_dialog_button_cancel": "Cancel",
|
||||
"account_delete_dialog_button_submit": "Permanently delete account",
|
||||
"account_delete_dialog_billing_warning": "Deleting your account also cancels your billing subscription immediately. You will not have access to the billing dashboard anymore.",
|
||||
"account_upgrade_dialog_title": "Change account tier",
|
||||
"account_upgrade_dialog_cancel_warning": "This will <strong>cancel your subscription</strong>, and downgrade your account on {{date}}. On that date, topic reservations as well as messages cached on the server <strong>will be deleted</strong>.",
|
||||
"account_upgrade_dialog_proration_info": "<strong>Proration</strong>: When switching between paid plans, the price difference will be charged or refunded in the next invoice. You will not receive another invoice until the end of the next billing period.",
|
||||
"account_upgrade_dialog_reservations_warning_one": "The selected tier allows fewer reserved topics than your current tier. Before changing your tier, <strong>please delete at least one reservation</strong>. You can remove reservations in the <Link>Settings</Link>.",
|
||||
"account_upgrade_dialog_reservations_warning_other": "The selected tier allows fewer reserved topics than your current tier. Before changing your tier, <strong>please delete at least {{count}} reservations</strong>. You can remove reservations in the <Link>Settings</Link>.",
|
||||
"account_upgrade_dialog_tier_features_reservations": "{{reservations}} reserved topics",
|
||||
"account_upgrade_dialog_tier_features_messages": "{{messages}} daily messages",
|
||||
"account_upgrade_dialog_tier_features_emails": "{{emails}} daily emails",
|
||||
"account_upgrade_dialog_tier_features_attachment_file_size": "{{filesize}} per file",
|
||||
"account_upgrade_dialog_tier_features_attachment_total_size": "{{totalsize}} total storage",
|
||||
"account_upgrade_dialog_tier_selected_label": "Selected",
|
||||
"account_upgrade_dialog_tier_current_label": "Current",
|
||||
"account_upgrade_dialog_button_cancel": "Cancel",
|
||||
"account_upgrade_dialog_button_redirect_signup": "Sign up now",
|
||||
"account_upgrade_dialog_button_pay_now": "Pay now and subscribe",
|
||||
|
|
|
@ -264,7 +264,6 @@ const AccountType = () => {
|
|||
const Stats = () => {
|
||||
const { t } = useTranslation();
|
||||
const { account } = useContext(AccountContext);
|
||||
const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false);
|
||||
|
||||
if (!account) {
|
||||
return <></>;
|
||||
|
@ -435,6 +434,7 @@ const DeleteAccount = () => {
|
|||
|
||||
const DeleteAccountDialog = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const { account } = useContext(AccountContext);
|
||||
const [username, setUsername] = useState("");
|
||||
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const buttonEnabled = username === session.username();
|
||||
|
@ -456,6 +456,9 @@ const DeleteAccountDialog = (props) => {
|
|||
fullWidth
|
||||
variant="standard"
|
||||
/>
|
||||
{account?.billing?.subscription &&
|
||||
<Alert severity="warning" sx={{mt: 1}}>{t("account_delete_dialog_billing_warning")}</Alert>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={props.onCancel}>{t("account_delete_dialog_button_cancel")}</Button>
|
||||
|
|
|
@ -3,7 +3,7 @@ import {useContext, useEffect, useState} from 'react';
|
|||
import {
|
||||
Alert,
|
||||
CardActions,
|
||||
CardContent,
|
||||
CardContent, Chip,
|
||||
FormControl,
|
||||
Select,
|
||||
Stack,
|
||||
|
@ -20,6 +20,7 @@ import prefs from "../app/Prefs";
|
|||
import {Paragraph} from "./styles";
|
||||
import EditIcon from '@mui/icons-material/Edit';
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import IconButton from "@mui/material/IconButton";
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import Container from "@mui/material/Container";
|
||||
|
@ -41,10 +42,12 @@ import routes from "./routes";
|
|||
import accountApi, {UnauthorizedError} from "../app/AccountApi";
|
||||
import {Pref, PrefGroup} from "./Pref";
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import {Public, PublicOff} from "@mui/icons-material";
|
||||
import {Check, Info, Public, PublicOff} from "@mui/icons-material";
|
||||
import DialogContentText from "@mui/material/DialogContentText";
|
||||
import ReserveTopicSelect from "./ReserveTopicSelect";
|
||||
import {AccountContext} from "./App";
|
||||
import {useOutletContext} from "react-router-dom";
|
||||
import subscriptionManager from "../app/SubscriptionManager";
|
||||
|
||||
const Preferences = () => {
|
||||
return (
|
||||
|
@ -543,6 +546,12 @@ const ReservationsTable = (props) => {
|
|||
const [dialogKey, setDialogKey] = useState(0);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [dialogReservation, setDialogReservation] = useState(null);
|
||||
const { subscriptions } = useOutletContext();
|
||||
const localSubscriptions = Object.assign(
|
||||
...subscriptions
|
||||
.filter(s => s.baseUrl === config.base_url)
|
||||
.map(s => ({[s.topic]: s}))
|
||||
);
|
||||
|
||||
const handleEditClick = (reservation) => {
|
||||
setDialogKey(prev => prev+1);
|
||||
|
@ -592,7 +601,9 @@ const ReservationsTable = (props) => {
|
|||
key={reservation.topic}
|
||||
sx={{'&:last-child td, &:last-child th': {border: 0}}}
|
||||
>
|
||||
<TableCell component="th" scope="row" sx={{paddingLeft: 0}} aria-label={t("prefs_reservations_table_topic_header")}>{reservation.topic}</TableCell>
|
||||
<TableCell component="th" scope="row" sx={{paddingLeft: 0}} aria-label={t("prefs_reservations_table_topic_header")}>
|
||||
{reservation.topic}
|
||||
</TableCell>
|
||||
<TableCell aria-label={t("prefs_reservations_table_access_header")}>
|
||||
{reservation.everyone === "read-write" &&
|
||||
<>
|
||||
|
@ -620,6 +631,9 @@ const ReservationsTable = (props) => {
|
|||
}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{!localSubscriptions[reservation.topic] &&
|
||||
<Chip icon={<Info/>} label="Not subscribed" color="primary" variant="outlined"/>
|
||||
}
|
||||
<IconButton onClick={() => handleEditClick(reservation)} aria-label={t("prefs_reservations_edit_button")}>
|
||||
<EditIcon/>
|
||||
</IconButton>
|
||||
|
|
|
@ -21,13 +21,14 @@ import {Check} from "@mui/icons-material";
|
|||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import Box from "@mui/material/Box";
|
||||
import {NavLink} from "react-router-dom";
|
||||
|
||||
const UpgradeDialog = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const { account } = useContext(AccountContext); // May be undefined!
|
||||
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
|
||||
const [tiers, setTiers] = useState(null);
|
||||
const [newTier, setNewTier] = useState(account?.tier?.code); // May be undefined
|
||||
const [newTierCode, setNewTierCode] = useState(account?.tier?.code); // May be undefined
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [errorText, setErrorText] = useState("");
|
||||
|
||||
|
@ -41,47 +42,56 @@ const UpgradeDialog = (props) => {
|
|||
return <></>;
|
||||
}
|
||||
|
||||
const currentTier = account?.tier?.code; // May be undefined
|
||||
let action, submitButtonLabel, submitButtonEnabled;
|
||||
const tiersMap = Object.assign(...tiers.map(tier => ({[tier.code]: tier})));
|
||||
const newTier = tiersMap[newTierCode]; // May be undefined
|
||||
const currentTier = account?.tier; // May be undefined
|
||||
const currentTierCode = currentTier?.code; // May be undefined
|
||||
|
||||
// Figure out buttons, labels and the submit action
|
||||
let submitAction, submitButtonLabel, banner;
|
||||
if (!account) {
|
||||
submitButtonLabel = t("account_upgrade_dialog_button_redirect_signup");
|
||||
submitButtonEnabled = true;
|
||||
action = Action.REDIRECT_SIGNUP;
|
||||
} else if (currentTier === newTier) {
|
||||
submitAction = Action.REDIRECT_SIGNUP;
|
||||
banner = null;
|
||||
} else if (currentTierCode === newTierCode) {
|
||||
submitButtonLabel = t("account_upgrade_dialog_button_update_subscription");
|
||||
submitButtonEnabled = false;
|
||||
action = null;
|
||||
} else if (!currentTier) {
|
||||
submitAction = null;
|
||||
banner = (currentTierCode) ? Banner.PRORATION_INFO : null;
|
||||
} else if (!currentTierCode) {
|
||||
submitButtonLabel = t("account_upgrade_dialog_button_pay_now");
|
||||
submitButtonEnabled = true;
|
||||
action = Action.CREATE_SUBSCRIPTION;
|
||||
} else if (!newTier) {
|
||||
submitAction = Action.CREATE_SUBSCRIPTION;
|
||||
banner = null;
|
||||
} else if (!newTierCode) {
|
||||
submitButtonLabel = t("account_upgrade_dialog_button_cancel_subscription");
|
||||
submitButtonEnabled = true;
|
||||
action = Action.CANCEL_SUBSCRIPTION;
|
||||
submitAction = Action.CANCEL_SUBSCRIPTION;
|
||||
banner = Banner.CANCEL_WARNING;
|
||||
} else {
|
||||
submitButtonLabel = t("account_upgrade_dialog_button_update_subscription");
|
||||
submitButtonEnabled = true;
|
||||
action = Action.UPDATE_SUBSCRIPTION;
|
||||
submitAction = Action.UPDATE_SUBSCRIPTION;
|
||||
banner = Banner.PRORATION_INFO;
|
||||
}
|
||||
|
||||
// Exceptional conditions
|
||||
if (loading) {
|
||||
submitButtonEnabled = false;
|
||||
submitAction = null;
|
||||
} else if (newTier?.code && account?.reservations.length > newTier?.limits.reservations) {
|
||||
submitAction = null;
|
||||
banner = Banner.RESERVATIONS_WARNING;
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (action === Action.REDIRECT_SIGNUP) {
|
||||
if (submitAction === Action.REDIRECT_SIGNUP) {
|
||||
window.location.href = routes.signup;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
setLoading(true);
|
||||
if (action === Action.CREATE_SUBSCRIPTION) {
|
||||
const response = await accountApi.createBillingSubscription(newTier);
|
||||
if (submitAction === Action.CREATE_SUBSCRIPTION) {
|
||||
const response = await accountApi.createBillingSubscription(newTierCode);
|
||||
window.location.href = response.redirect_url;
|
||||
} else if (action === Action.UPDATE_SUBSCRIPTION) {
|
||||
await accountApi.updateBillingSubscription(newTier);
|
||||
} else if (action === Action.CANCEL_SUBSCRIPTION) {
|
||||
} else if (submitAction === Action.UPDATE_SUBSCRIPTION) {
|
||||
await accountApi.updateBillingSubscription(newTierCode);
|
||||
} else if (submitAction === Action.CANCEL_SUBSCRIPTION) {
|
||||
await accountApi.deleteBillingSubscription();
|
||||
}
|
||||
props.onCancel();
|
||||
|
@ -116,27 +126,39 @@ const UpgradeDialog = (props) => {
|
|||
<TierCard
|
||||
key={`tierCard${tier.code || '_free'}`}
|
||||
tier={tier}
|
||||
selected={newTier === tier.code} // tier.code may be undefined!
|
||||
onClick={() => setNewTier(tier.code)} // tier.code may be undefined!
|
||||
current={currentTierCode === tier.code} // tier.code or currentTierCode may be undefined!
|
||||
selected={newTierCode === tier.code} // tier.code may be undefined!
|
||||
onClick={() => setNewTierCode(tier.code)} // tier.code may be undefined!
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{action === Action.CANCEL_SUBSCRIPTION &&
|
||||
{banner === Banner.CANCEL_WARNING &&
|
||||
<Alert severity="warning">
|
||||
<Trans
|
||||
i18nKey="account_upgrade_dialog_cancel_warning"
|
||||
values={{ date: formatShortDate(account?.billing?.paid_until || 0) }} />
|
||||
</Alert>
|
||||
}
|
||||
{currentTier && (!action || action === Action.UPDATE_SUBSCRIPTION) &&
|
||||
{banner === Banner.PRORATION_INFO &&
|
||||
<Alert severity="info">
|
||||
<Trans i18nKey="account_upgrade_dialog_proration_info" />
|
||||
</Alert>
|
||||
}
|
||||
{banner === Banner.RESERVATIONS_WARNING &&
|
||||
<Alert severity="warning">
|
||||
<Trans
|
||||
i18nKey="account_upgrade_dialog_reservations_warning"
|
||||
count={account?.reservations.length - newTier?.limits.reservations}
|
||||
components={{
|
||||
Link: <NavLink to={routes.settings}/>,
|
||||
}}
|
||||
/>
|
||||
</Alert>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogFooter status={errorText}>
|
||||
<Button onClick={props.onCancel}>{t("account_upgrade_dialog_button_cancel")}</Button>
|
||||
<Button onClick={handleSubmit} disabled={!submitButtonEnabled}>{submitButtonLabel}</Button>
|
||||
<Button onClick={handleSubmit} disabled={!submitAction}>{submitButtonLabel}</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
);
|
||||
|
@ -144,8 +166,19 @@ const UpgradeDialog = (props) => {
|
|||
|
||||
const TierCard = (props) => {
|
||||
const { t } = useTranslation();
|
||||
const cardStyle = (props.selected) ? { background: "#eee", border: "2px solid #338574" } : { border: "2px solid transparent" };
|
||||
const tier = props.tier;
|
||||
let cardStyle, labelStyle, labelText;
|
||||
if (props.selected) {
|
||||
cardStyle = { background: "#eee", border: "2px solid #338574" };
|
||||
labelStyle = { background: "#338574", color: "white" };
|
||||
labelText = t("account_upgrade_dialog_tier_selected_label");
|
||||
} else if (props.current) {
|
||||
cardStyle = { border: "2px solid #eee" };
|
||||
labelStyle = { background: "#eee", color: "black" };
|
||||
labelText = t("account_upgrade_dialog_tier_current_label");
|
||||
} else {
|
||||
cardStyle = { border: "2px solid transparent" };
|
||||
}
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
|
@ -163,16 +196,15 @@ const TierCard = (props) => {
|
|||
<Card sx={{ height: "100%" }}>
|
||||
<CardActionArea sx={{ height: "100%" }}>
|
||||
<CardContent onClick={props.onClick} sx={{ height: "100%" }}>
|
||||
{props.selected &&
|
||||
{labelStyle &&
|
||||
<div style={{
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
right: "15px",
|
||||
padding: "2px 10px",
|
||||
background: "#338574",
|
||||
color: "white",
|
||||
borderRadius: "3px",
|
||||
}}>{t("account_upgrade_dialog_tier_selected_label")}</div>
|
||||
...labelStyle
|
||||
}}>{labelText}</div>
|
||||
}
|
||||
<Typography variant="h5" component="div">
|
||||
{tier.name || t("account_usage_tier_free")}
|
||||
|
@ -217,10 +249,17 @@ const FeatureItem = (props) => {
|
|||
};
|
||||
|
||||
const Action = {
|
||||
REDIRECT_SIGNUP: 0,
|
||||
CREATE_SUBSCRIPTION: 1,
|
||||
UPDATE_SUBSCRIPTION: 2,
|
||||
CANCEL_SUBSCRIPTION: 3
|
||||
REDIRECT_SIGNUP: 1,
|
||||
CREATE_SUBSCRIPTION: 2,
|
||||
UPDATE_SUBSCRIPTION: 3,
|
||||
CANCEL_SUBSCRIPTION: 4
|
||||
};
|
||||
|
||||
const Banner = {
|
||||
CANCEL_WARNING: 1,
|
||||
PRORATION_INFO: 2,
|
||||
RESERVATIONS_WARNING: 3
|
||||
};
|
||||
|
||||
|
||||
export default UpgradeDialog;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue