2022-12-16 16:07:04 +13:00
|
|
|
import * as React from "react";
|
2023-02-03 09:19:37 +13:00
|
|
|
import { useContext, useState } from "react";
|
2023-01-28 17:10:59 +13:00
|
|
|
import {
|
2023-05-18 02:39:15 +12:00
|
|
|
Alert,
|
2023-01-28 17:10:59 +13:00
|
|
|
CardActions,
|
2023-05-18 02:39:15 +12:00
|
|
|
CardContent,
|
|
|
|
Chip,
|
|
|
|
FormControl,
|
|
|
|
FormControlLabel,
|
2023-02-03 09:19:37 +13:00
|
|
|
LinearProgress,
|
|
|
|
Link,
|
2023-05-18 02:39:15 +12:00
|
|
|
Portal,
|
|
|
|
Radio,
|
|
|
|
RadioGroup,
|
2023-02-03 09:19:37 +13:00
|
|
|
Select,
|
|
|
|
Snackbar,
|
2023-01-28 17:10:59 +13:00
|
|
|
Stack,
|
2023-02-03 09:19:37 +13:00
|
|
|
Table,
|
|
|
|
TableBody,
|
|
|
|
TableCell,
|
2023-01-28 17:10:59 +13:00
|
|
|
TableHead,
|
|
|
|
TableRow,
|
|
|
|
useMediaQuery,
|
2023-05-24 22:25:20 +12:00
|
|
|
Tooltip,
|
|
|
|
Typography,
|
|
|
|
Container,
|
|
|
|
Card,
|
|
|
|
Button,
|
|
|
|
Dialog,
|
|
|
|
DialogTitle,
|
|
|
|
DialogContent,
|
|
|
|
TextField,
|
|
|
|
IconButton,
|
|
|
|
MenuItem,
|
|
|
|
DialogContentText,
|
2023-01-28 17:10:59 +13:00
|
|
|
} from "@mui/material";
|
2022-12-16 16:07:04 +13:00
|
|
|
import EditIcon from "@mui/icons-material/Edit";
|
2023-01-28 17:10:59 +13:00
|
|
|
import { Trans, useTranslation } from "react-i18next";
|
2022-12-18 07:49:32 +13:00
|
|
|
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
2023-01-06 09:20:44 +13:00
|
|
|
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
|
2023-01-10 09:40:46 +13:00
|
|
|
import i18n from "i18next";
|
|
|
|
import humanizeDuration from "humanize-duration";
|
|
|
|
import CelebrationIcon from "@mui/icons-material/Celebration";
|
2023-01-28 17:10:59 +13:00
|
|
|
import CloseIcon from "@mui/icons-material/Close";
|
2023-05-18 02:39:15 +12:00
|
|
|
import { ContentCopy, Public } from "@mui/icons-material";
|
2023-05-13 12:01:12 +12:00
|
|
|
import AddIcon from "@mui/icons-material/Add";
|
2022-12-16 16:07:04 +13:00
|
|
|
import routes from "./routes";
|
2023-02-03 09:19:37 +13:00
|
|
|
import { formatBytes, formatShortDate, formatShortDateTime, openUrl } from "../app/utils";
|
2023-02-22 16:44:30 +13:00
|
|
|
import accountApi, { LimitBasis, Role, SubscriptionInterval, SubscriptionStatus } from "../app/AccountApi";
|
2022-12-30 02:20:53 +13:00
|
|
|
import { Pref, PrefGroup } from "./Pref";
|
2023-05-25 07:36:01 +12:00
|
|
|
import getDb from "../app/getDb";
|
2023-01-10 09:40:46 +13:00
|
|
|
import UpgradeDialog from "./UpgradeDialog";
|
2023-01-10 14:37:13 +13:00
|
|
|
import { AccountContext } from "./App";
|
2023-01-22 14:52:16 +13:00
|
|
|
import DialogFooter from "./DialogFooter";
|
2023-01-28 17:10:59 +13:00
|
|
|
import { Paragraph } from "./styles";
|
2023-02-03 09:19:37 +13:00
|
|
|
import { IncorrectPasswordError, UnauthorizedError } from "../app/errors";
|
2023-05-08 14:28:07 +12:00
|
|
|
import { ProChip } from "./SubscriptionPopup";
|
2022-12-16 16:07:04 +13:00
|
|
|
import theme from "./theme";
|
2023-05-13 12:01:12 +12:00
|
|
|
import session from "../app/Session";
|
2022-12-16 16:07:04 +13:00
|
|
|
|
|
|
|
const Account = () => {
|
2022-12-20 03:59:32 +13:00
|
|
|
if (!session.exists()) {
|
|
|
|
window.location.href = routes.app;
|
|
|
|
return <></>;
|
|
|
|
}
|
2022-12-16 16:07:04 +13:00
|
|
|
return (
|
|
|
|
<Container maxWidth="md" sx={{ marginTop: 3, marginBottom: 3 }}>
|
|
|
|
<Stack spacing={3}>
|
|
|
|
<Basics />
|
2022-12-18 07:49:32 +13:00
|
|
|
<Stats />
|
2023-01-28 17:10:59 +13:00
|
|
|
<Tokens />
|
2022-12-18 07:49:32 +13:00
|
|
|
<Delete />
|
2022-12-16 16:07:04 +13:00
|
|
|
</Stack>
|
|
|
|
</Container>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const Basics = () => {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
2022-12-29 20:32:05 +13:00
|
|
|
<Card sx={{ p: 3 }} aria-label={t("account_basics_title")}>
|
2022-12-16 16:07:04 +13:00
|
|
|
<Typography variant="h5" sx={{ marginBottom: 2 }}>
|
2022-12-29 20:32:05 +13:00
|
|
|
{t("account_basics_title")}
|
2022-12-16 16:07:04 +13:00
|
|
|
</Typography>
|
|
|
|
<PrefGroup>
|
2022-12-18 07:49:32 +13:00
|
|
|
<Username />
|
2022-12-16 16:07:04 +13:00
|
|
|
<ChangePassword />
|
2023-05-13 12:01:12 +12:00
|
|
|
<PhoneNumbers />
|
2023-01-17 10:35:37 +13:00
|
|
|
<AccountType />
|
2022-12-18 07:49:32 +13:00
|
|
|
</PrefGroup>
|
|
|
|
</Card>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const Username = () => {
|
|
|
|
const { t } = useTranslation();
|
2023-01-10 14:37:13 +13:00
|
|
|
const { account } = useContext(AccountContext);
|
2022-12-30 02:20:53 +13:00
|
|
|
const labelId = "prefUsername";
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2022-12-18 07:49:32 +13:00
|
|
|
return (
|
2022-12-30 02:20:53 +13:00
|
|
|
<Pref labelId={labelId} title={t("account_basics_username_title")} description={t("account_basics_username_description")}>
|
|
|
|
<div aria-labelledby={labelId}>
|
2022-12-18 07:49:32 +13:00
|
|
|
{session.username()}
|
2023-01-31 07:10:45 +13:00
|
|
|
{account?.role === Role.ADMIN ? (
|
2022-12-29 20:32:05 +13:00
|
|
|
<>
|
|
|
|
{" "}
|
|
|
|
<Tooltip title={t("account_basics_username_admin_tooltip")}>
|
|
|
|
<span style={{ cursor: "default" }}>👑</span>
|
|
|
|
</Tooltip>
|
|
|
|
</>
|
2022-12-18 07:49:32 +13:00
|
|
|
) : (
|
|
|
|
""
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</Pref>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-12-16 16:07:04 +13:00
|
|
|
const ChangePassword = () => {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const [dialogKey, setDialogKey] = useState(0);
|
|
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
2022-12-30 02:20:53 +13:00
|
|
|
const labelId = "prefChangePassword";
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2022-12-16 16:07:04 +13:00
|
|
|
const handleDialogOpen = () => {
|
|
|
|
setDialogKey((prev) => prev + 1);
|
|
|
|
setDialogOpen(true);
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-22 14:52:16 +13:00
|
|
|
const handleDialogClose = () => {
|
2022-12-16 16:07:04 +13:00
|
|
|
setDialogOpen(false);
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2022-12-16 16:07:04 +13:00
|
|
|
return (
|
2022-12-30 02:20:53 +13:00
|
|
|
<Pref labelId={labelId} title={t("account_basics_password_title")} description={t("account_basics_password_description")}>
|
|
|
|
<div aria-labelledby={labelId}>
|
2022-12-18 07:49:32 +13:00
|
|
|
<Typography color="gray" sx={{ float: "left", fontSize: "0.7rem", lineHeight: "3.5" }}>
|
|
|
|
⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤
|
|
|
|
</Typography>
|
2022-12-29 20:32:05 +13:00
|
|
|
<IconButton onClick={handleDialogOpen} aria-label={t("account_basics_password_description")}>
|
2022-12-18 07:49:32 +13:00
|
|
|
<EditIcon />
|
|
|
|
</IconButton>
|
|
|
|
</div>
|
2022-12-16 16:07:04 +13:00
|
|
|
<ChangePasswordDialog key={`changePasswordDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
|
|
|
|
</Pref>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const ChangePasswordDialog = (props) => {
|
|
|
|
const { t } = useTranslation();
|
2023-02-03 09:19:37 +13:00
|
|
|
const [error, setError] = useState("");
|
2023-01-22 14:52:16 +13:00
|
|
|
const [currentPassword, setCurrentPassword] = useState("");
|
2022-12-16 16:07:04 +13:00
|
|
|
const [newPassword, setNewPassword] = useState("");
|
|
|
|
const [confirmPassword, setConfirmPassword] = useState("");
|
|
|
|
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-22 14:52:16 +13:00
|
|
|
const handleDialogSubmit = async () => {
|
|
|
|
try {
|
|
|
|
console.debug(`[Account] Changing password`);
|
|
|
|
await accountApi.changePassword(currentPassword, newPassword);
|
|
|
|
props.onClose();
|
|
|
|
} catch (e) {
|
|
|
|
console.log(`[Account] Error changing password`, e);
|
2023-02-03 09:19:37 +13:00
|
|
|
if (e instanceof IncorrectPasswordError) {
|
|
|
|
setError(t("account_basics_password_dialog_current_password_incorrect"));
|
|
|
|
} else if (e instanceof UnauthorizedError) {
|
2023-01-22 14:52:16 +13:00
|
|
|
session.resetAndRedirect(routes.login);
|
2023-02-03 09:19:37 +13:00
|
|
|
} else {
|
|
|
|
setError(e.message);
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
2023-01-22 14:52:16 +13:00
|
|
|
}
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2022-12-16 16:07:04 +13:00
|
|
|
return (
|
|
|
|
<Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
|
2022-12-29 20:32:05 +13:00
|
|
|
<DialogTitle>{t("account_basics_password_dialog_title")}</DialogTitle>
|
2022-12-16 16:07:04 +13:00
|
|
|
<DialogContent>
|
2023-01-22 14:52:16 +13:00
|
|
|
<TextField
|
|
|
|
margin="dense"
|
|
|
|
id="current-password"
|
|
|
|
label={t("account_basics_password_dialog_current_password_label")}
|
|
|
|
aria-label={t("account_basics_password_dialog_current_password_label")}
|
|
|
|
type="password"
|
|
|
|
value={currentPassword}
|
|
|
|
onChange={(ev) => setCurrentPassword(ev.target.value)}
|
|
|
|
fullWidth
|
|
|
|
variant="standard"
|
|
|
|
/>
|
2022-12-16 16:07:04 +13:00
|
|
|
<TextField
|
|
|
|
margin="dense"
|
|
|
|
id="new-password"
|
2022-12-29 20:32:05 +13:00
|
|
|
label={t("account_basics_password_dialog_new_password_label")}
|
|
|
|
aria-label={t("account_basics_password_dialog_new_password_label")}
|
2022-12-16 16:07:04 +13:00
|
|
|
type="password"
|
|
|
|
value={newPassword}
|
|
|
|
onChange={(ev) => setNewPassword(ev.target.value)}
|
|
|
|
fullWidth
|
|
|
|
variant="standard"
|
|
|
|
/>
|
|
|
|
<TextField
|
|
|
|
margin="dense"
|
|
|
|
id="confirm"
|
2022-12-29 20:32:05 +13:00
|
|
|
label={t("account_basics_password_dialog_confirm_password_label")}
|
|
|
|
aria-label={t("account_basics_password_dialog_confirm_password_label")}
|
2022-12-16 16:07:04 +13:00
|
|
|
type="password"
|
|
|
|
value={confirmPassword}
|
|
|
|
onChange={(ev) => setConfirmPassword(ev.target.value)}
|
|
|
|
fullWidth
|
|
|
|
variant="standard"
|
|
|
|
/>
|
|
|
|
</DialogContent>
|
2023-02-03 09:19:37 +13:00
|
|
|
<DialogFooter status={error}>
|
2023-02-10 09:24:12 +13:00
|
|
|
<Button onClick={props.onClose}>{t("common_cancel")}</Button>
|
2023-01-22 14:52:16 +13:00
|
|
|
<Button
|
|
|
|
onClick={handleDialogSubmit}
|
|
|
|
disabled={newPassword.length === 0 || currentPassword.length === 0 || newPassword !== confirmPassword}
|
|
|
|
>
|
|
|
|
{t("account_basics_password_dialog_button_submit")}
|
|
|
|
</Button>
|
|
|
|
</DialogFooter>
|
2022-12-16 16:07:04 +13:00
|
|
|
</Dialog>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-01-17 10:35:37 +13:00
|
|
|
const AccountType = () => {
|
2022-12-29 20:32:05 +13:00
|
|
|
const { t } = useTranslation();
|
2023-01-10 14:37:13 +13:00
|
|
|
const { account } = useContext(AccountContext);
|
2023-01-17 10:35:37 +13:00
|
|
|
const [upgradeDialogKey, setUpgradeDialogKey] = useState(0);
|
2023-01-10 09:40:46 +13:00
|
|
|
const [upgradeDialogOpen, setUpgradeDialogOpen] = useState(false);
|
2023-02-03 09:19:37 +13:00
|
|
|
const [showPortalError, setShowPortalError] = useState(false);
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2022-12-29 20:32:05 +13:00
|
|
|
if (!account) {
|
|
|
|
return <></>;
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
|
|
|
|
2023-01-17 10:35:37 +13:00
|
|
|
const handleUpgradeClick = () => {
|
|
|
|
setUpgradeDialogKey((k) => k + 1);
|
|
|
|
setUpgradeDialogOpen(true);
|
2023-05-24 07:13:01 +12:00
|
|
|
};
|
|
|
|
|
2023-01-15 00:43:44 +13:00
|
|
|
const handleManageBilling = async () => {
|
2023-05-24 07:13:01 +12:00
|
|
|
try {
|
2023-01-15 00:43:44 +13:00
|
|
|
const response = await accountApi.createBillingPortalSession();
|
2023-01-16 17:29:46 +13:00
|
|
|
window.open(response.redirect_url, "billing_portal");
|
2023-01-15 00:43:44 +13:00
|
|
|
} catch (e) {
|
2023-02-03 09:19:37 +13:00
|
|
|
console.log(`[Account] Error opening billing portal`, e);
|
|
|
|
if (e instanceof UnauthorizedError) {
|
2023-01-15 00:43:44 +13:00
|
|
|
session.resetAndRedirect(routes.login);
|
2023-05-24 07:13:01 +12:00
|
|
|
} else {
|
2023-02-03 09:19:37 +13:00
|
|
|
setShowPortalError(true);
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
2022-12-29 20:32:05 +13:00
|
|
|
}
|
2023-05-24 07:13:01 +12:00
|
|
|
};
|
|
|
|
|
2023-01-17 10:35:37 +13:00
|
|
|
let accountType;
|
|
|
|
if (account.role === Role.ADMIN) {
|
|
|
|
const tierSuffix = account.tier
|
|
|
|
? t("account_basics_tier_admin_suffix_with_tier", {
|
2023-02-10 09:24:12 +13:00
|
|
|
tier: account.tier.name,
|
2023-05-24 07:13:01 +12:00
|
|
|
})
|
2023-01-17 10:35:37 +13:00
|
|
|
: t("account_basics_tier_admin_suffix_no_tier");
|
2023-02-10 09:24:12 +13:00
|
|
|
accountType = `${t("account_basics_tier_admin")} ${tierSuffix}`;
|
2023-01-17 10:35:37 +13:00
|
|
|
} else if (!account.tier) {
|
2023-02-10 09:24:12 +13:00
|
|
|
accountType = config.enable_payments ? t("account_basics_tier_free") : t("account_basics_tier_basic");
|
2023-05-24 07:13:01 +12:00
|
|
|
} else {
|
2023-01-17 10:35:37 +13:00
|
|
|
accountType = account.tier.name;
|
2023-02-22 16:44:30 +13:00
|
|
|
if (account.billing?.interval === SubscriptionInterval.MONTH) {
|
2023-01-17 10:35:37 +13:00
|
|
|
accountType += ` (${t("account_basics_tier_interval_monthly")})`;
|
|
|
|
} else if (account.billing?.interval === SubscriptionInterval.YEAR) {
|
|
|
|
accountType += ` (${t("account_basics_tier_interval_yearly")})`;
|
|
|
|
}
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
|
|
|
|
2023-01-15 00:43:44 +13:00
|
|
|
return (
|
2023-05-24 07:13:01 +12:00
|
|
|
<Pref
|
2023-01-15 00:43:44 +13:00
|
|
|
alignTop={account.billing?.status === SubscriptionStatus.PAST_DUE || account.billing?.cancel_at > 0}
|
2023-02-10 09:24:12 +13:00
|
|
|
title={t("account_basics_tier_title")}
|
|
|
|
description={t("account_basics_tier_description")}
|
2023-05-24 07:13:01 +12:00
|
|
|
>
|
|
|
|
<div>
|
2023-01-17 10:35:37 +13:00
|
|
|
{accountType}
|
2023-02-10 09:24:12 +13:00
|
|
|
{account.billing?.paid_until && !account.billing?.cancel_at && (
|
2023-05-24 07:13:01 +12:00
|
|
|
<Tooltip
|
2023-02-10 09:24:12 +13:00
|
|
|
title={t("account_basics_tier_paid_until", {
|
|
|
|
date: formatShortDate(account.billing?.paid_until),
|
2023-05-24 07:13:01 +12:00
|
|
|
})}
|
|
|
|
>
|
|
|
|
<span>
|
2023-02-10 09:24:12 +13:00
|
|
|
<InfoIcon />
|
2023-05-24 07:13:01 +12:00
|
|
|
</span>
|
2023-02-10 09:24:12 +13:00
|
|
|
</Tooltip>
|
|
|
|
)}
|
2023-02-22 16:44:30 +13:00
|
|
|
{config.enable_payments && account.role === Role.USER && !account.billing?.subscription && (
|
2023-01-31 07:10:45 +13:00
|
|
|
<Button
|
|
|
|
variant="outlined"
|
|
|
|
size="small"
|
|
|
|
startIcon={<CelebrationIcon sx={{ color: "#55b86e" }} />}
|
|
|
|
onClick={handleUpgradeClick}
|
|
|
|
sx={{ ml: 1 }}
|
|
|
|
>
|
2023-02-10 09:24:12 +13:00
|
|
|
{t("account_basics_tier_upgrade_button")}
|
2023-01-17 10:35:37 +13:00
|
|
|
</Button>
|
|
|
|
)}
|
2023-01-31 07:10:45 +13:00
|
|
|
{config.enable_payments && account.role === Role.USER && account.billing?.subscription && (
|
2023-01-17 10:35:37 +13:00
|
|
|
<Button variant="outlined" size="small" onClick={handleUpgradeClick} sx={{ ml: 1 }}>
|
2023-02-10 09:24:12 +13:00
|
|
|
{t("account_basics_tier_change_button")}
|
|
|
|
</Button>
|
2023-01-17 10:35:37 +13:00
|
|
|
)}
|
2023-01-31 07:10:45 +13:00
|
|
|
{config.enable_payments && account.role === Role.USER && account.billing?.customer && (
|
2023-01-17 10:35:37 +13:00
|
|
|
<Button variant="outlined" size="small" onClick={handleManageBilling} sx={{ ml: 1 }}>
|
2023-02-10 09:24:12 +13:00
|
|
|
{t("account_basics_tier_manage_billing_button")}
|
|
|
|
</Button>
|
|
|
|
)}
|
|
|
|
{config.enable_payments && (
|
|
|
|
<UpgradeDialog
|
|
|
|
key={`upgradeDialogFromAccount${upgradeDialogKey}`}
|
|
|
|
open={upgradeDialogOpen}
|
|
|
|
onCancel={() => setUpgradeDialogOpen(false)}
|
|
|
|
/>
|
2023-01-17 10:35:37 +13:00
|
|
|
)}
|
|
|
|
</div>
|
2023-01-31 07:10:45 +13:00
|
|
|
{account.billing?.status === SubscriptionStatus.PAST_DUE && (
|
2023-02-10 09:24:12 +13:00
|
|
|
<Alert severity="error" sx={{ mt: 1 }}>
|
|
|
|
{t("account_basics_tier_payment_overdue")}
|
|
|
|
</Alert>
|
2023-01-17 10:35:37 +13:00
|
|
|
)}
|
|
|
|
{account.billing?.cancel_at > 0 && (
|
2023-02-10 09:24:12 +13:00
|
|
|
<Alert severity="warning" sx={{ mt: 1 }}>
|
|
|
|
{t("account_basics_tier_canceled_subscription", {
|
|
|
|
date: formatShortDate(account.billing.cancel_at),
|
|
|
|
})}
|
|
|
|
</Alert>
|
2023-01-17 10:35:37 +13:00
|
|
|
)}
|
2023-02-03 09:19:37 +13:00
|
|
|
<Portal>
|
|
|
|
<Snackbar
|
|
|
|
open={showPortalError}
|
|
|
|
autoHideDuration={3000}
|
|
|
|
onClose={() => setShowPortalError(false)}
|
|
|
|
message={t("account_usage_cannot_create_portal_session")}
|
|
|
|
/>
|
|
|
|
</Portal>
|
2023-01-17 10:35:37 +13:00
|
|
|
</Pref>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-05-13 12:01:12 +12:00
|
|
|
const PhoneNumbers = () => {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const { account } = useContext(AccountContext);
|
2023-05-13 13:47:41 +12:00
|
|
|
const [dialogKey, setDialogKey] = useState(0);
|
|
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
|
|
const [snackOpen, setSnackOpen] = useState(false);
|
2023-05-13 12:01:12 +12:00
|
|
|
const labelId = "prefPhoneNumbers";
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-05-13 13:47:41 +12:00
|
|
|
const handleDialogOpen = () => {
|
|
|
|
setDialogKey((prev) => prev + 1);
|
|
|
|
setDialogOpen(true);
|
2023-05-13 12:01:12 +12:00
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-05-13 13:47:41 +12:00
|
|
|
const handleDialogClose = () => {
|
|
|
|
setDialogOpen(false);
|
2023-05-24 07:13:01 +12:00
|
|
|
};
|
|
|
|
|
2023-05-13 13:47:41 +12:00
|
|
|
const handleCopy = (phoneNumber) => {
|
|
|
|
navigator.clipboard.writeText(phoneNumber);
|
|
|
|
setSnackOpen(true);
|
2023-05-24 07:13:01 +12:00
|
|
|
};
|
|
|
|
|
2023-05-13 13:47:41 +12:00
|
|
|
const handleDelete = async (phoneNumber) => {
|
2023-05-24 07:13:01 +12:00
|
|
|
try {
|
2023-05-13 13:47:41 +12:00
|
|
|
await accountApi.deletePhoneNumber(phoneNumber);
|
|
|
|
} catch (e) {
|
|
|
|
console.log(`[Account] Error deleting phone number`, e);
|
|
|
|
if (e instanceof UnauthorizedError) {
|
|
|
|
session.resetAndRedirect(routes.login);
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
|
|
|
}
|
2023-05-13 12:01:12 +12:00
|
|
|
};
|
|
|
|
|
2023-05-13 13:47:41 +12:00
|
|
|
if (!config.enable_calls) {
|
|
|
|
return null;
|
|
|
|
}
|
2023-05-13 12:01:12 +12:00
|
|
|
|
2023-05-13 13:47:41 +12:00
|
|
|
if (account?.limits.calls === 0) {
|
|
|
|
return (
|
2023-05-24 07:13:01 +12:00
|
|
|
<Pref
|
2023-05-13 13:47:41 +12:00
|
|
|
title={
|
2023-05-24 07:13:01 +12:00
|
|
|
<>
|
2023-05-13 13:47:41 +12:00
|
|
|
{t("account_basics_phone_numbers_title")}
|
|
|
|
{config.enable_payments && <ProChip />}
|
2023-05-24 07:13:01 +12:00
|
|
|
</>
|
2023-05-13 13:47:41 +12:00
|
|
|
}
|
2023-05-13 12:01:12 +12:00
|
|
|
description={t("account_basics_phone_numbers_description")}
|
2023-05-24 07:13:01 +12:00
|
|
|
>
|
2023-05-16 12:42:43 +12:00
|
|
|
<em>{t("account_usage_calls_none")}</em>
|
2023-05-24 07:13:01 +12:00
|
|
|
</Pref>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
2023-05-13 12:01:12 +12:00
|
|
|
<Pref labelId={labelId} title={t("account_basics_phone_numbers_title")} description={t("account_basics_phone_numbers_description")}>
|
|
|
|
<div aria-labelledby={labelId}>
|
2023-05-13 13:47:41 +12:00
|
|
|
{account?.phone_numbers?.map((phoneNumber) => (
|
2023-05-24 07:13:01 +12:00
|
|
|
<Chip
|
|
|
|
label={
|
2023-05-13 13:47:41 +12:00
|
|
|
<Tooltip title={t("common_copy_to_clipboard")}>
|
|
|
|
<span>{phoneNumber}</span>
|
|
|
|
</Tooltip>
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
2023-05-13 13:47:41 +12:00
|
|
|
variant="outlined"
|
|
|
|
onClick={() => handleCopy(phoneNumber)}
|
|
|
|
onDelete={() => handleDelete(phoneNumber)}
|
2023-05-24 07:13:01 +12:00
|
|
|
/>
|
|
|
|
))}
|
2023-05-13 13:47:41 +12:00
|
|
|
{!account?.phone_numbers && <em>{t("account_basics_phone_numbers_no_phone_numbers_yet")}</em>}
|
|
|
|
<IconButton onClick={handleDialogOpen}>
|
|
|
|
<AddIcon />
|
|
|
|
</IconButton>
|
2023-05-24 07:13:01 +12:00
|
|
|
</div>
|
2023-05-13 13:47:41 +12:00
|
|
|
<AddPhoneNumberDialog key={`addPhoneNumberDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
|
2023-05-24 07:13:01 +12:00
|
|
|
<Portal>
|
2023-05-13 13:47:41 +12:00
|
|
|
<Snackbar
|
|
|
|
open={snackOpen}
|
|
|
|
autoHideDuration={3000}
|
|
|
|
onClose={() => setSnackOpen(false)}
|
|
|
|
message={t("account_basics_phone_numbers_copied_to_clipboard")}
|
2023-05-24 07:13:01 +12:00
|
|
|
/>
|
2023-05-13 13:47:41 +12:00
|
|
|
</Portal>
|
2023-05-24 07:13:01 +12:00
|
|
|
</Pref>
|
|
|
|
);
|
2023-05-13 12:01:12 +12:00
|
|
|
};
|
|
|
|
|
2023-05-13 13:47:41 +12:00
|
|
|
const AddPhoneNumberDialog = (props) => {
|
2023-01-17 10:35:37 +13:00
|
|
|
const { t } = useTranslation();
|
2023-05-13 13:47:41 +12:00
|
|
|
const [error, setError] = useState("");
|
|
|
|
const [phoneNumber, setPhoneNumber] = useState("");
|
2023-05-17 14:27:48 +12:00
|
|
|
const [channel, setChannel] = useState("sms");
|
2023-05-13 13:47:41 +12:00
|
|
|
const [code, setCode] = useState("");
|
|
|
|
const [sending, setSending] = useState(false);
|
|
|
|
const [verificationCodeSent, setVerificationCodeSent] = useState(false);
|
|
|
|
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-05-13 12:01:12 +12:00
|
|
|
const verifyPhone = async () => {
|
|
|
|
try {
|
2023-05-13 13:47:41 +12:00
|
|
|
setSending(true);
|
2023-05-13 12:01:12 +12:00
|
|
|
await accountApi.verifyPhoneNumber(phoneNumber, channel);
|
|
|
|
setVerificationCodeSent(true);
|
|
|
|
} catch (e) {
|
|
|
|
console.log(`[Account] Error sending verification`, e);
|
|
|
|
if (e instanceof UnauthorizedError) {
|
|
|
|
session.resetAndRedirect(routes.login);
|
|
|
|
} else {
|
2023-05-13 13:47:41 +12:00
|
|
|
setError(e.message);
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
2023-05-13 13:47:41 +12:00
|
|
|
} finally {
|
|
|
|
setSending(false);
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-05-13 13:47:41 +12:00
|
|
|
const checkVerifyPhone = async () => {
|
2023-05-24 07:13:01 +12:00
|
|
|
try {
|
2023-05-13 13:47:41 +12:00
|
|
|
setSending(true);
|
|
|
|
await accountApi.addPhoneNumber(phoneNumber, code);
|
|
|
|
props.onClose();
|
|
|
|
} catch (e) {
|
|
|
|
console.log(`[Account] Error confirming verification`, e);
|
|
|
|
if (e instanceof UnauthorizedError) {
|
|
|
|
session.resetAndRedirect(routes.login);
|
2023-05-24 07:13:01 +12:00
|
|
|
} else {
|
2023-05-13 13:47:41 +12:00
|
|
|
setError(e.message);
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
2023-05-13 13:47:41 +12:00
|
|
|
} finally {
|
|
|
|
setSending(false);
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-05-24 20:20:15 +12:00
|
|
|
const handleDialogSubmit = async () => {
|
|
|
|
if (!verificationCodeSent) {
|
|
|
|
await verifyPhone();
|
|
|
|
} else {
|
|
|
|
await checkVerifyPhone();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
|
if (verificationCodeSent) {
|
|
|
|
setVerificationCodeSent(false);
|
|
|
|
setCode("");
|
|
|
|
} else {
|
|
|
|
props.onClose();
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-05-24 07:13:01 +12:00
|
|
|
return (
|
2023-05-13 13:47:41 +12:00
|
|
|
<Dialog open={props.open} onClose={props.onCancel} fullScreen={fullScreen}>
|
|
|
|
<DialogTitle>{t("account_basics_phone_numbers_dialog_title")}</DialogTitle>
|
|
|
|
<DialogContent>
|
|
|
|
<DialogContentText>{t("account_basics_phone_numbers_dialog_description")}</DialogContentText>
|
2023-05-17 14:27:48 +12:00
|
|
|
{!verificationCodeSent && (
|
2023-01-29 14:29:06 +13:00
|
|
|
<div style={{ display: "flex" }}>
|
2023-05-13 13:47:41 +12:00
|
|
|
<TextField
|
|
|
|
margin="dense"
|
|
|
|
label={t("account_basics_phone_numbers_dialog_number_label")}
|
|
|
|
aria-label={t("account_basics_phone_numbers_dialog_number_label")}
|
|
|
|
placeholder={t("account_basics_phone_numbers_dialog_number_placeholder")}
|
|
|
|
type="tel"
|
|
|
|
value={phoneNumber}
|
|
|
|
onChange={(ev) => setPhoneNumber(ev.target.value)}
|
2023-05-17 14:27:48 +12:00
|
|
|
inputProps={{ inputMode: "tel", pattern: "+[0-9]*" }}
|
2023-05-13 13:47:41 +12:00
|
|
|
variant="standard"
|
|
|
|
sx={{ flexGrow: 1 }}
|
|
|
|
/>
|
|
|
|
<FormControl sx={{ flexWrap: "nowrap" }}>
|
|
|
|
<RadioGroup row sx={{ flexGrow: 1, marginTop: "8px", marginLeft: "5px" }}>
|
2023-05-17 14:27:48 +12:00
|
|
|
<FormControlLabel
|
|
|
|
value="sms"
|
2023-05-13 13:47:41 +12:00
|
|
|
control={<Radio checked={channel === "sms"} onChange={(e) => setChannel(e.target.value)} />}
|
|
|
|
label={t("account_basics_phone_numbers_dialog_channel_sms")}
|
|
|
|
/>
|
|
|
|
<FormControlLabel
|
|
|
|
value="call"
|
2023-05-17 14:27:48 +12:00
|
|
|
control={<Radio checked={channel === "call"} onChange={(e) => setChannel(e.target.value)} />}
|
|
|
|
label={t("account_basics_phone_numbers_dialog_channel_call")}
|
|
|
|
sx={{ marginRight: 0 }}
|
2023-05-24 07:13:01 +12:00
|
|
|
/>
|
2023-05-17 14:27:48 +12:00
|
|
|
</RadioGroup>
|
|
|
|
</FormControl>
|
2023-05-24 07:13:01 +12:00
|
|
|
</div>
|
|
|
|
)}
|
2023-05-13 13:47:41 +12:00
|
|
|
{verificationCodeSent && (
|
|
|
|
<TextField
|
|
|
|
margin="dense"
|
|
|
|
label={t("account_basics_phone_numbers_dialog_code_label")}
|
|
|
|
aria-label={t("account_basics_phone_numbers_dialog_code_label")}
|
|
|
|
placeholder={t("account_basics_phone_numbers_dialog_code_placeholder")}
|
|
|
|
type="text"
|
|
|
|
value={code}
|
|
|
|
onChange={(ev) => setCode(ev.target.value)}
|
|
|
|
fullWidth
|
|
|
|
inputProps={{ inputMode: "numeric", pattern: "[0-9]*" }}
|
|
|
|
variant="standard"
|
2023-05-24 07:13:01 +12:00
|
|
|
/>
|
|
|
|
)}
|
2023-05-13 13:47:41 +12:00
|
|
|
</DialogContent>
|
|
|
|
<DialogFooter status={error}>
|
|
|
|
<Button onClick={handleCancel}>{verificationCodeSent ? t("common_back") : t("common_cancel")}</Button>
|
|
|
|
<Button onClick={handleDialogSubmit} disabled={sending || !/^\+\d+$/.test(phoneNumber)}>
|
2023-05-17 14:27:48 +12:00
|
|
|
{!verificationCodeSent && channel === "sms" && t("account_basics_phone_numbers_dialog_verify_button_sms")}
|
|
|
|
{!verificationCodeSent && channel === "call" && t("account_basics_phone_numbers_dialog_verify_button_call")}
|
|
|
|
{verificationCodeSent && t("account_basics_phone_numbers_dialog_check_verification_button")}
|
2023-05-13 13:47:41 +12:00
|
|
|
</Button>
|
|
|
|
</DialogFooter>
|
|
|
|
</Dialog>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-01-17 10:35:37 +13:00
|
|
|
const Stats = () => {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const { account } = useContext(AccountContext);
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-17 10:35:37 +13:00
|
|
|
if (!account) {
|
|
|
|
return <></>;
|
|
|
|
}
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-17 10:35:37 +13:00
|
|
|
const normalize = (value, max) => Math.min((value / max) * 100, 100);
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2022-12-29 20:32:05 +13:00
|
|
|
return (
|
|
|
|
<Card sx={{ p: 3 }} aria-label={t("account_usage_title")}>
|
|
|
|
<Typography variant="h5" sx={{ marginBottom: 2 }}>
|
2023-05-08 14:28:07 +12:00
|
|
|
{t("account_usage_title")}
|
2023-01-28 17:10:59 +13:00
|
|
|
</Typography>
|
2022-12-29 20:32:05 +13:00
|
|
|
<PrefGroup>
|
2023-05-08 14:28:07 +12:00
|
|
|
{(account.role === Role.ADMIN || account.limits.reservations > 0) && (
|
|
|
|
<Pref title={t("account_usage_reservations_title")}>
|
2023-05-24 07:13:01 +12:00
|
|
|
<div>
|
2023-01-31 07:10:45 +13:00
|
|
|
<Typography variant="body2" sx={{ float: "left" }}>
|
2023-05-08 14:28:07 +12:00
|
|
|
{account.stats.reservations.toLocaleString()}
|
2023-01-28 17:10:59 +13:00
|
|
|
</Typography>
|
2023-01-31 07:10:45 +13:00
|
|
|
<Typography variant="body2" sx={{ float: "right" }}>
|
|
|
|
{account.role === Role.USER
|
|
|
|
? t("account_usage_of_limit", {
|
2023-05-08 14:28:07 +12:00
|
|
|
limit: account.limits.reservations.toLocaleString(),
|
2023-05-24 07:13:01 +12:00
|
|
|
})
|
2023-01-31 07:10:45 +13:00
|
|
|
: t("account_usage_unlimited")}
|
2023-01-28 17:10:59 +13:00
|
|
|
</Typography>
|
2023-05-24 07:13:01 +12:00
|
|
|
</div>
|
2023-01-05 14:34:22 +13:00
|
|
|
<LinearProgress
|
|
|
|
variant="determinate"
|
2023-05-08 14:28:07 +12:00
|
|
|
value={
|
|
|
|
account.role === Role.USER && account.limits.reservations > 0
|
|
|
|
? normalize(account.stats.reservations, account.limits.reservations)
|
|
|
|
: 100
|
|
|
|
}
|
2023-05-24 07:13:01 +12:00
|
|
|
/>
|
|
|
|
</Pref>
|
|
|
|
)}
|
|
|
|
<Pref
|
|
|
|
title={
|
|
|
|
<>
|
2022-12-29 20:32:05 +13:00
|
|
|
{t("account_usage_messages_title")}
|
2023-05-08 14:28:07 +12:00
|
|
|
<Tooltip title={t("account_usage_limits_reset_daily")}>
|
2023-05-24 07:13:01 +12:00
|
|
|
<span>
|
2023-05-08 14:28:07 +12:00
|
|
|
<InfoIcon />
|
2023-05-24 07:13:01 +12:00
|
|
|
</span>
|
2023-01-28 17:10:59 +13:00
|
|
|
</Tooltip>
|
2023-05-24 07:13:01 +12:00
|
|
|
</>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<div>
|
2023-01-31 07:10:45 +13:00
|
|
|
<Typography variant="body2" sx={{ float: "left" }}>
|
2023-05-08 14:28:07 +12:00
|
|
|
{account.stats.messages.toLocaleString()}
|
2022-12-29 20:32:05 +13:00
|
|
|
</Typography>
|
2023-05-08 14:28:07 +12:00
|
|
|
<Typography variant="body2" sx={{ float: "right" }}>
|
|
|
|
{account.role === Role.USER
|
|
|
|
? t("account_usage_of_limit", {
|
|
|
|
limit: account.limits.messages.toLocaleString(),
|
|
|
|
})
|
|
|
|
: t("account_usage_unlimited")}
|
|
|
|
</Typography>
|
|
|
|
</div>
|
2023-01-31 07:10:45 +13:00
|
|
|
<LinearProgress
|
|
|
|
variant="determinate"
|
|
|
|
value={account.role === Role.USER ? normalize(account.stats.messages, account.limits.messages) : 100}
|
|
|
|
/>
|
2022-12-29 20:32:05 +13:00
|
|
|
</Pref>
|
2023-05-18 02:58:28 +12:00
|
|
|
{config.enable_emails && (
|
|
|
|
<Pref
|
|
|
|
title={
|
|
|
|
<>
|
|
|
|
{t("account_usage_emails_title")}
|
|
|
|
<Tooltip title={t("account_usage_limits_reset_daily")}>
|
|
|
|
<span>
|
|
|
|
<InfoIcon />
|
|
|
|
</span>
|
|
|
|
</Tooltip>
|
|
|
|
</>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<div>
|
|
|
|
<Typography variant="body2" sx={{ float: "left" }}>
|
|
|
|
{account.stats.emails.toLocaleString()}
|
|
|
|
</Typography>
|
|
|
|
<Typography variant="body2" sx={{ float: "right" }}>
|
|
|
|
{account.role === Role.USER
|
|
|
|
? t("account_usage_of_limit", {
|
|
|
|
limit: account.limits.emails.toLocaleString(),
|
2023-05-08 14:28:07 +12:00
|
|
|
})
|
|
|
|
: t("account_usage_unlimited")}
|
|
|
|
</Typography>
|
2023-05-24 07:13:01 +12:00
|
|
|
</div>
|
2023-05-13 12:01:12 +12:00
|
|
|
<LinearProgress
|
|
|
|
variant="determinate"
|
|
|
|
value={account.role === Role.USER ? normalize(account.stats.emails, account.limits.emails) : 100}
|
|
|
|
/>
|
2023-05-24 11:29:47 +12:00
|
|
|
</Pref>
|
|
|
|
)}
|
2023-05-13 12:01:12 +12:00
|
|
|
{config.enable_calls && (account.role === Role.ADMIN || account.limits.calls > 0) && (
|
2023-05-24 11:29:47 +12:00
|
|
|
<Pref
|
|
|
|
title={
|
|
|
|
<>
|
2023-01-10 09:40:46 +13:00
|
|
|
{t("account_usage_calls_title")}
|
2023-05-08 14:28:07 +12:00
|
|
|
<Tooltip title={t("account_usage_limits_reset_daily")}>
|
2023-05-24 11:29:47 +12:00
|
|
|
<span>
|
2023-05-08 14:28:07 +12:00
|
|
|
<InfoIcon />
|
2023-05-24 11:29:47 +12:00
|
|
|
</span>
|
2023-01-28 17:10:59 +13:00
|
|
|
</Tooltip>
|
2023-05-24 11:29:47 +12:00
|
|
|
</>
|
|
|
|
}
|
|
|
|
>
|
|
|
|
<div>
|
2023-01-31 07:10:45 +13:00
|
|
|
<Typography variant="body2" sx={{ float: "left" }}>
|
|
|
|
{account.stats.calls.toLocaleString()}
|
2023-01-28 17:10:59 +13:00
|
|
|
</Typography>
|
2023-01-31 07:10:45 +13:00
|
|
|
<Typography variant="body2" sx={{ float: "right" }}>
|
|
|
|
{account.role === Role.USER
|
|
|
|
? t("account_usage_of_limit", {
|
2023-01-10 09:40:46 +13:00
|
|
|
limit: account.limits.calls.toLocaleString(),
|
2023-05-24 11:29:47 +12:00
|
|
|
})
|
2023-01-31 07:10:45 +13:00
|
|
|
: t("account_usage_unlimited")}
|
2023-01-28 17:10:59 +13:00
|
|
|
</Typography>
|
2023-05-24 11:29:47 +12:00
|
|
|
</div>
|
2023-05-08 14:28:07 +12:00
|
|
|
<LinearProgress
|
|
|
|
variant="determinate"
|
2023-05-13 12:01:12 +12:00
|
|
|
value={account.role === Role.USER && account.limits.calls > 0 ? normalize(account.stats.calls, account.limits.calls) : 100}
|
2023-05-24 07:13:01 +12:00
|
|
|
/>
|
|
|
|
</Pref>
|
|
|
|
)}
|
|
|
|
<Pref
|
|
|
|
alignTop
|
2023-01-10 09:40:46 +13:00
|
|
|
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, {
|
2023-05-16 11:49:34 +12:00
|
|
|
language: i18n.resolvedLanguage,
|
2023-01-10 09:40:46 +13:00
|
|
|
fallbacks: ["en"],
|
2023-05-24 07:13:01 +12:00
|
|
|
}),
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
<div>
|
2022-12-29 20:32:05 +13:00
|
|
|
<Typography variant="body2" sx={{ float: "left" }}>
|
|
|
|
{formatBytes(account.stats.attachment_total_size)}
|
|
|
|
</Typography>
|
2023-05-08 14:28:07 +12:00
|
|
|
<Typography variant="body2" sx={{ float: "right" }}>
|
2023-01-31 07:10:45 +13:00
|
|
|
{account.role === Role.USER
|
2023-05-08 14:28:07 +12:00
|
|
|
? t("account_usage_of_limit", {
|
2023-01-31 07:10:45 +13:00
|
|
|
limit: formatBytes(account.limits.attachment_total_size),
|
2023-05-24 07:13:01 +12:00
|
|
|
})
|
2023-05-18 02:58:28 +12:00
|
|
|
: t("account_usage_unlimited")}
|
2022-12-29 20:32:05 +13:00
|
|
|
</Typography>
|
2023-05-24 07:13:01 +12:00
|
|
|
</div>
|
2023-01-05 14:34:22 +13:00
|
|
|
<LinearProgress
|
|
|
|
variant="determinate"
|
2023-01-31 07:10:45 +13:00
|
|
|
value={account.role === Role.USER ? normalize(account.stats.attachment_total_size, account.limits.attachment_total_size) : 100}
|
2023-05-24 07:13:01 +12:00
|
|
|
/>
|
|
|
|
</Pref>
|
2023-05-08 14:28:07 +12:00
|
|
|
{config.enable_reservations && account.role === Role.USER && account.limits.reservations === 0 && (
|
2023-05-24 07:13:01 +12:00
|
|
|
<Pref
|
|
|
|
title={
|
|
|
|
<>
|
2023-05-08 14:28:07 +12:00
|
|
|
{t("account_usage_reservations_title")}
|
|
|
|
{config.enable_payments && <ProChip />}
|
2023-05-24 07:13:01 +12:00
|
|
|
</>
|
|
|
|
}
|
|
|
|
>
|
2023-05-08 14:28:07 +12:00
|
|
|
<em>{t("account_usage_reservations_none")}</em>
|
2023-05-24 07:13:01 +12:00
|
|
|
</Pref>
|
|
|
|
)}
|
2023-05-08 14:28:07 +12:00
|
|
|
{config.enable_calls && account.role === Role.USER && account.limits.calls === 0 && (
|
2023-05-24 07:13:01 +12:00
|
|
|
<Pref
|
|
|
|
title={
|
|
|
|
<>
|
2023-05-08 14:28:07 +12:00
|
|
|
{t("account_usage_calls_title")}
|
|
|
|
{config.enable_payments && <ProChip />}
|
2023-05-24 07:13:01 +12:00
|
|
|
</>
|
|
|
|
}
|
|
|
|
>
|
2023-05-08 14:28:07 +12:00
|
|
|
<em>{t("account_usage_calls_none")}</em>
|
2023-05-24 07:13:01 +12:00
|
|
|
</Pref>
|
|
|
|
)}
|
2022-12-29 20:32:05 +13:00
|
|
|
</PrefGroup>
|
2023-01-31 07:10:45 +13:00
|
|
|
{account.role === Role.USER && account.limits.basis === LimitBasis.IP && (
|
2023-05-13 13:47:41 +12:00
|
|
|
<Typography variant="body1">{t("account_usage_basis_ip_description")}</Typography>
|
2023-05-24 07:13:01 +12:00
|
|
|
)}
|
2022-12-29 20:32:05 +13:00
|
|
|
</Card>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-01-06 09:20:44 +13:00
|
|
|
const InfoIcon = () => (
|
|
|
|
<InfoOutlinedIcon
|
|
|
|
sx={{
|
2023-01-17 04:35:12 +13:00
|
|
|
verticalAlign: "middle",
|
2023-01-06 09:20:44 +13:00
|
|
|
width: "18px",
|
|
|
|
marginLeft: "4px",
|
|
|
|
color: "gray",
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
);
|
2023-01-28 17:10:59 +13:00
|
|
|
|
|
|
|
const Tokens = () => {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const { account } = useContext(AccountContext);
|
|
|
|
const [dialogKey, setDialogKey] = useState(0);
|
|
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
|
|
const tokens = account?.tokens || [];
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
const handleCreateClick = () => {
|
|
|
|
setDialogKey((prev) => prev + 1);
|
|
|
|
setDialogOpen(true);
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
const handleDialogClose = () => {
|
|
|
|
setDialogOpen(false);
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
return (
|
|
|
|
<Card sx={{ padding: 1 }} aria-label={t("prefs_users_title")}>
|
|
|
|
<CardContent sx={{ paddingBottom: 1 }}>
|
|
|
|
<Typography variant="h5" sx={{ marginBottom: 2 }}>
|
|
|
|
{t("account_tokens_title")}
|
|
|
|
</Typography>
|
|
|
|
<Paragraph>
|
|
|
|
<Trans
|
|
|
|
i18nKey="account_tokens_description"
|
|
|
|
components={{
|
2023-02-15 08:26:30 +13:00
|
|
|
Link: <Link href="/docs/publish/#access-tokens" />,
|
2023-01-28 17:10:59 +13:00
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</Paragraph>
|
|
|
|
{tokens?.length > 0 && <TokensTable tokens={tokens} />}
|
|
|
|
</CardContent>
|
|
|
|
<CardActions>
|
|
|
|
<Button onClick={handleCreateClick}>{t("account_tokens_table_create_token_button")}</Button>
|
|
|
|
</CardActions>
|
|
|
|
<TokenDialog key={`tokenDialogCreate${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
|
|
|
|
</Card>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const TokensTable = (props) => {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const [snackOpen, setSnackOpen] = useState(false);
|
|
|
|
const [upsertDialogKey, setUpsertDialogKey] = useState(0);
|
|
|
|
const [upsertDialogOpen, setUpsertDialogOpen] = useState(false);
|
|
|
|
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
|
|
|
|
const [selectedToken, setSelectedToken] = useState(null);
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
const tokens = (props.tokens || []).sort((a, b) => {
|
|
|
|
if (a.token === session.token()) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (b.token === session.token()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return a.token.localeCompare(b.token);
|
|
|
|
});
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
const handleEditClick = (token) => {
|
|
|
|
setUpsertDialogKey((prev) => prev + 1);
|
|
|
|
setSelectedToken(token);
|
|
|
|
setUpsertDialogOpen(true);
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
const handleDialogClose = () => {
|
|
|
|
setUpsertDialogOpen(false);
|
|
|
|
setDeleteDialogOpen(false);
|
|
|
|
setSelectedToken(null);
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
const handleDeleteClick = async (token) => {
|
|
|
|
setSelectedToken(token);
|
|
|
|
setDeleteDialogOpen(true);
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
const handleCopy = async (token) => {
|
|
|
|
await navigator.clipboard.writeText(token);
|
2023-05-13 13:47:41 +12:00
|
|
|
setSnackOpen(true);
|
2023-01-28 17:10:59 +13:00
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
return (
|
|
|
|
<Table size="small" aria-label={t("account_tokens_title")}>
|
|
|
|
<TableHead>
|
|
|
|
<TableRow>
|
|
|
|
<TableCell sx={{ paddingLeft: 0 }}>{t("account_tokens_table_token_header")}</TableCell>
|
|
|
|
<TableCell>{t("account_tokens_table_label_header")}</TableCell>
|
|
|
|
<TableCell>{t("account_tokens_table_expires_header")}</TableCell>
|
|
|
|
<TableCell>{t("account_tokens_table_last_access_header")}</TableCell>
|
|
|
|
<TableCell />
|
|
|
|
</TableRow>
|
|
|
|
</TableHead>
|
|
|
|
<TableBody>
|
|
|
|
{tokens.map((token) => (
|
2023-01-29 14:29:06 +13:00
|
|
|
<TableRow key={token.token} sx={{ "&:last-child td, &:last-child th": { border: 0 } }}>
|
|
|
|
<TableCell
|
|
|
|
component="th"
|
|
|
|
scope="row"
|
|
|
|
sx={{ paddingLeft: 0, whiteSpace: "nowrap" }}
|
|
|
|
aria-label={t("account_tokens_table_token_header")}
|
|
|
|
>
|
2023-05-24 07:13:01 +12:00
|
|
|
<span>
|
2023-01-29 14:29:06 +13:00
|
|
|
<span style={{ fontFamily: "Monospace", fontSize: "0.9rem" }}>{token.token.slice(0, 12)}</span>
|
2023-01-28 17:10:59 +13:00
|
|
|
...
|
2023-05-13 13:47:41 +12:00
|
|
|
<Tooltip title={t("common_copy_to_clipboard")} placement="right">
|
2023-01-28 17:10:59 +13:00
|
|
|
<IconButton onClick={() => handleCopy(token.token)}>
|
|
|
|
<ContentCopy />
|
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
|
|
|
</span>
|
|
|
|
</TableCell>
|
|
|
|
<TableCell aria-label={t("account_tokens_table_label_header")}>
|
|
|
|
{token.token === session.token() && <em>{t("account_tokens_table_current_session")}</em>}
|
|
|
|
{token.token !== session.token() && (token.label || "-")}
|
|
|
|
</TableCell>
|
2023-01-29 16:13:43 +13:00
|
|
|
<TableCell sx={{ whiteSpace: "nowrap" }} aria-label={t("account_tokens_table_expires_header")}>
|
2023-01-28 17:10:59 +13:00
|
|
|
{token.expires ? formatShortDateTime(token.expires) : <em>{t("account_tokens_table_never_expires")}</em>}
|
|
|
|
</TableCell>
|
2023-01-29 16:13:43 +13:00
|
|
|
<TableCell sx={{ whiteSpace: "nowrap" }} aria-label={t("account_tokens_table_last_access_header")}>
|
2023-01-29 14:29:06 +13:00
|
|
|
<div style={{ display: "flex", alignItems: "center" }}>
|
|
|
|
<span>{formatShortDateTime(token.last_access)}</span>
|
|
|
|
<Tooltip
|
|
|
|
title={t("account_tokens_table_last_origin_tooltip", {
|
|
|
|
ip: token.last_origin,
|
|
|
|
})}
|
|
|
|
>
|
|
|
|
<IconButton onClick={() => openUrl(`https://whatismyipaddress.com/ip/${token.last_origin}`)}>
|
|
|
|
<Public />
|
|
|
|
</IconButton>
|
|
|
|
</Tooltip>
|
|
|
|
</div>
|
|
|
|
</TableCell>
|
|
|
|
<TableCell align="right" sx={{ whiteSpace: "nowrap" }}>
|
2023-01-28 17:10:59 +13:00
|
|
|
{token.token !== session.token() && (
|
|
|
|
<>
|
|
|
|
<IconButton onClick={() => handleEditClick(token)} aria-label={t("account_tokens_dialog_title_edit")}>
|
|
|
|
<EditIcon />
|
|
|
|
</IconButton>
|
|
|
|
<IconButton onClick={() => handleDeleteClick(token)} aria-label={t("account_tokens_dialog_title_delete")}>
|
|
|
|
<CloseIcon />
|
|
|
|
</IconButton>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
{token.token === session.token() && (
|
|
|
|
<Tooltip title={t("account_tokens_table_cannot_delete_or_edit")}>
|
|
|
|
<span>
|
|
|
|
<IconButton disabled>
|
|
|
|
<EditIcon />
|
|
|
|
</IconButton>
|
|
|
|
<IconButton disabled>
|
|
|
|
<CloseIcon />
|
|
|
|
</IconButton>
|
|
|
|
</span>
|
|
|
|
</Tooltip>
|
|
|
|
)}
|
|
|
|
</TableCell>
|
|
|
|
</TableRow>
|
|
|
|
))}
|
|
|
|
</TableBody>
|
|
|
|
<Portal>
|
|
|
|
<Snackbar
|
|
|
|
open={snackOpen}
|
|
|
|
autoHideDuration={3000}
|
|
|
|
onClose={() => setSnackOpen(false)}
|
|
|
|
message={t("account_tokens_table_copied_to_clipboard")}
|
|
|
|
/>
|
|
|
|
</Portal>
|
|
|
|
<TokenDialog key={`tokenDialogEdit${upsertDialogKey}`} open={upsertDialogOpen} token={selectedToken} onClose={handleDialogClose} />
|
|
|
|
<TokenDeleteDialog open={deleteDialogOpen} token={selectedToken} onClose={handleDialogClose} />
|
|
|
|
</Table>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const TokenDialog = (props) => {
|
|
|
|
const { t } = useTranslation();
|
2023-02-03 09:19:37 +13:00
|
|
|
const [error, setError] = useState("");
|
2023-01-28 17:10:59 +13:00
|
|
|
const [label, setLabel] = useState(props.token?.label || "");
|
|
|
|
const [expires, setExpires] = useState(props.token ? -1 : 0);
|
|
|
|
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
|
|
|
|
const editMode = !!props.token;
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
const handleSubmit = async () => {
|
|
|
|
try {
|
|
|
|
if (editMode) {
|
|
|
|
await accountApi.updateToken(props.token.token, label, expires);
|
|
|
|
} else {
|
|
|
|
await accountApi.createToken(label, expires);
|
|
|
|
}
|
|
|
|
props.onClose();
|
|
|
|
} catch (e) {
|
|
|
|
console.log(`[Account] Error creating token`, e);
|
2023-02-03 09:19:37 +13:00
|
|
|
if (e instanceof UnauthorizedError) {
|
2023-01-28 17:10:59 +13:00
|
|
|
session.resetAndRedirect(routes.login);
|
2023-02-03 09:19:37 +13:00
|
|
|
} else {
|
|
|
|
setError(e.message);
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
2023-01-28 17:10:59 +13:00
|
|
|
}
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
return (
|
|
|
|
<Dialog open={props.open} onClose={props.onClose} maxWidth="sm" fullWidth fullScreen={fullScreen}>
|
|
|
|
<DialogTitle>{editMode ? t("account_tokens_dialog_title_edit") : t("account_tokens_dialog_title_create")}</DialogTitle>
|
|
|
|
<DialogContent>
|
|
|
|
<TextField
|
|
|
|
margin="dense"
|
|
|
|
id="token-label"
|
|
|
|
label={t("account_tokens_dialog_label")}
|
|
|
|
aria-label={t("account_delete_dialog_label")}
|
|
|
|
type="text"
|
|
|
|
value={label}
|
|
|
|
onChange={(ev) => setLabel(ev.target.value)}
|
|
|
|
fullWidth
|
|
|
|
variant="standard"
|
|
|
|
/>
|
|
|
|
<FormControl fullWidth variant="standard" sx={{ mt: 1 }}>
|
|
|
|
<Select value={expires} onChange={(ev) => setExpires(ev.target.value)} aria-label={t("account_tokens_dialog_expires_label")}>
|
|
|
|
{editMode && <MenuItem value={-1}>{t("account_tokens_dialog_expires_unchanged")}</MenuItem>}
|
|
|
|
<MenuItem value={0}>{t("account_tokens_dialog_expires_never")}</MenuItem>
|
|
|
|
<MenuItem value={21600}>{t("account_tokens_dialog_expires_x_hours", { hours: 6 })}</MenuItem>
|
|
|
|
<MenuItem value={43200}>{t("account_tokens_dialog_expires_x_hours", { hours: 12 })}</MenuItem>
|
|
|
|
<MenuItem value={259200}>{t("account_tokens_dialog_expires_x_days", { days: 3 })}</MenuItem>
|
|
|
|
<MenuItem value={604800}>{t("account_tokens_dialog_expires_x_days", { days: 7 })}</MenuItem>
|
|
|
|
<MenuItem value={2592000}>{t("account_tokens_dialog_expires_x_days", { days: 30 })}</MenuItem>
|
|
|
|
<MenuItem value={7776000}>{t("account_tokens_dialog_expires_x_days", { days: 90 })}</MenuItem>
|
|
|
|
<MenuItem value={15552000}>{t("account_tokens_dialog_expires_x_days", { days: 180 })}</MenuItem>
|
|
|
|
</Select>
|
|
|
|
</FormControl>
|
|
|
|
</DialogContent>
|
2023-02-03 09:19:37 +13:00
|
|
|
<DialogFooter status={error}>
|
2023-01-28 17:10:59 +13:00
|
|
|
<Button onClick={props.onClose}>{t("account_tokens_dialog_button_cancel")}</Button>
|
|
|
|
<Button onClick={handleSubmit}>
|
|
|
|
{editMode ? t("account_tokens_dialog_button_update") : t("account_tokens_dialog_button_create")}
|
|
|
|
</Button>
|
|
|
|
</DialogFooter>
|
|
|
|
</Dialog>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const TokenDeleteDialog = (props) => {
|
|
|
|
const { t } = useTranslation();
|
2023-05-25 13:32:15 +12:00
|
|
|
const [error, setError] = useState("");
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
const handleSubmit = async () => {
|
|
|
|
try {
|
|
|
|
await accountApi.deleteToken(props.token.token);
|
|
|
|
props.onClose();
|
|
|
|
} catch (e) {
|
|
|
|
console.log(`[Account] Error deleting token`, e);
|
2023-02-03 09:19:37 +13:00
|
|
|
if (e instanceof UnauthorizedError) {
|
2023-01-28 17:10:59 +13:00
|
|
|
session.resetAndRedirect(routes.login);
|
2023-05-25 13:32:15 +12:00
|
|
|
} else {
|
|
|
|
setError(e.message);
|
2023-01-28 17:10:59 +13:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-28 17:10:59 +13:00
|
|
|
return (
|
|
|
|
<Dialog open={props.open} onClose={props.onClose}>
|
|
|
|
<DialogTitle>{t("account_tokens_delete_dialog_title")}</DialogTitle>
|
|
|
|
<DialogContent>
|
|
|
|
<DialogContentText>
|
|
|
|
<Trans i18nKey="account_tokens_delete_dialog_description" />
|
|
|
|
</DialogContentText>
|
|
|
|
</DialogContent>
|
2023-05-25 13:32:15 +12:00
|
|
|
<DialogFooter status={error}>
|
2023-01-28 17:10:59 +13:00
|
|
|
<Button onClick={props.onClose}>{t("common_cancel")}</Button>
|
|
|
|
<Button onClick={handleSubmit} color="error">
|
|
|
|
{t("account_tokens_delete_dialog_submit_button")}
|
|
|
|
</Button>
|
2023-02-03 09:19:37 +13:00
|
|
|
</DialogFooter>
|
2023-01-28 17:10:59 +13:00
|
|
|
</Dialog>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-12-29 20:32:05 +13:00
|
|
|
const Delete = () => {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
return (
|
|
|
|
<Card sx={{ p: 3 }} aria-label={t("account_delete_title")}>
|
|
|
|
<Typography variant="h5" sx={{ marginBottom: 2 }}>
|
|
|
|
{t("account_delete_title")}
|
|
|
|
</Typography>
|
|
|
|
<PrefGroup>
|
|
|
|
<DeleteAccount />
|
|
|
|
</PrefGroup>
|
|
|
|
</Card>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2022-12-16 16:07:04 +13:00
|
|
|
const DeleteAccount = () => {
|
|
|
|
const { t } = useTranslation();
|
|
|
|
const [dialogKey, setDialogKey] = useState(0);
|
|
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2022-12-16 16:07:04 +13:00
|
|
|
const handleDialogOpen = () => {
|
|
|
|
setDialogKey((prev) => prev + 1);
|
|
|
|
setDialogOpen(true);
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-24 04:58:39 +13:00
|
|
|
const handleDialogClose = () => {
|
2022-12-16 16:07:04 +13:00
|
|
|
setDialogOpen(false);
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2022-12-16 16:07:04 +13:00
|
|
|
return (
|
2022-12-29 20:32:05 +13:00
|
|
|
<Pref title={t("account_delete_title")} description={t("account_delete_description")}>
|
2022-12-18 07:49:32 +13:00
|
|
|
<div>
|
|
|
|
<Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
|
2022-12-29 20:32:05 +13:00
|
|
|
{t("account_delete_title")}
|
2022-12-18 07:49:32 +13:00
|
|
|
</Button>
|
|
|
|
</div>
|
2022-12-16 16:07:04 +13:00
|
|
|
<DeleteAccountDialog key={`deleteAccountDialog${dialogKey}`} open={dialogOpen} onClose={handleDialogClose} />
|
|
|
|
</Pref>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const DeleteAccountDialog = (props) => {
|
|
|
|
const { t } = useTranslation();
|
2023-01-21 16:47:37 +13:00
|
|
|
const { account } = useContext(AccountContext);
|
2023-02-03 09:19:37 +13:00
|
|
|
const [error, setError] = useState("");
|
2023-01-24 04:58:39 +13:00
|
|
|
const [password, setPassword] = useState("");
|
2022-12-16 16:07:04 +13:00
|
|
|
const fullScreen = useMediaQuery(theme.breakpoints.down("sm"));
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2023-01-24 04:58:39 +13:00
|
|
|
const handleSubmit = async () => {
|
|
|
|
try {
|
|
|
|
await accountApi.delete(password);
|
2023-05-25 07:36:01 +12:00
|
|
|
await getDb().delete();
|
2023-01-24 04:58:39 +13:00
|
|
|
console.debug(`[Account] Account deleted`);
|
|
|
|
session.resetAndRedirect(routes.app);
|
|
|
|
} catch (e) {
|
|
|
|
console.log(`[Account] Error deleting account`, e);
|
2023-02-03 09:19:37 +13:00
|
|
|
if (e instanceof IncorrectPasswordError) {
|
|
|
|
setError(t("account_basics_password_dialog_current_password_incorrect"));
|
|
|
|
} else if (e instanceof UnauthorizedError) {
|
2023-01-24 04:58:39 +13:00
|
|
|
session.resetAndRedirect(routes.login);
|
2023-02-03 09:19:37 +13:00
|
|
|
} else {
|
|
|
|
setError(e.message);
|
2023-05-24 07:13:01 +12:00
|
|
|
}
|
2023-01-24 04:58:39 +13:00
|
|
|
}
|
|
|
|
};
|
2023-05-24 07:13:01 +12:00
|
|
|
|
2022-12-16 16:07:04 +13:00
|
|
|
return (
|
2023-01-24 04:58:39 +13:00
|
|
|
<Dialog open={props.open} onClose={props.onClose} fullScreen={fullScreen}>
|
2022-12-29 20:32:05 +13:00
|
|
|
<DialogTitle>{t("account_delete_title")}</DialogTitle>
|
2022-12-16 16:07:04 +13:00
|
|
|
<DialogContent>
|
2023-01-24 04:58:39 +13:00
|
|
|
<Typography variant="body1">{t("account_delete_dialog_description")}</Typography>
|
2022-12-16 16:07:04 +13:00
|
|
|
<TextField
|
|
|
|
margin="dense"
|
|
|
|
id="account-delete-confirm"
|
2023-01-24 04:58:39 +13:00
|
|
|
label={t("account_delete_dialog_label")}
|
|
|
|
aria-label={t("account_delete_dialog_label")}
|
|
|
|
type="password"
|
|
|
|
value={password}
|
|
|
|
onChange={(ev) => setPassword(ev.target.value)}
|
2022-12-16 16:07:04 +13:00
|
|
|
fullWidth
|
|
|
|
variant="standard"
|
|
|
|
/>
|
2023-01-21 16:47:37 +13:00
|
|
|
{account?.billing?.subscription && (
|
|
|
|
<Alert severity="warning" sx={{ mt: 1 }}>
|
|
|
|
{t("account_delete_dialog_billing_warning")}
|
|
|
|
</Alert>
|
|
|
|
)}
|
2022-12-16 16:07:04 +13:00
|
|
|
</DialogContent>
|
2023-02-03 09:19:37 +13:00
|
|
|
<DialogFooter status={error}>
|
2023-01-24 04:58:39 +13:00
|
|
|
<Button onClick={props.onClose}>{t("account_delete_dialog_button_cancel")}</Button>
|
|
|
|
<Button onClick={handleSubmit} color="error" disabled={password.length === 0}>
|
|
|
|
{t("account_delete_dialog_button_submit")}
|
2023-05-24 07:13:01 +12:00
|
|
|
</Button>
|
2023-01-24 04:58:39 +13:00
|
|
|
</DialogFooter>
|
2022-12-16 16:07:04 +13:00
|
|
|
</Dialog>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default Account;
|