1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2024-11-12 22:57:59 +01:00

Account delete, mock user stats UI

This commit is contained in:
binwiederhier 2022-12-17 13:49:32 -05:00
parent 81a8efcca3
commit 8752680233
3 changed files with 114 additions and 31 deletions

View file

@ -36,6 +36,7 @@ import (
/* /*
TODO TODO
return rate limit information in account stats
expire tokens expire tokens
auto-refresh tokens from UI auto-refresh tokens from UI
reserve topics reserve topics
@ -48,7 +49,8 @@ import (
- Pricing - Pricing
- change email - change email
- -
Polishing:
aria-label for everything
*/ */

View file

@ -1,5 +1,7 @@
import * as React from 'react'; import * as React from 'react';
import {Stack, useMediaQuery} from "@mui/material"; import {useState} from 'react';
import {LinearProgress, Stack, useMediaQuery} from "@mui/material";
import Tooltip from '@mui/material/Tooltip';
import Typography from "@mui/material/Typography"; import Typography from "@mui/material/Typography";
import EditIcon from '@mui/icons-material/Edit'; import EditIcon from '@mui/icons-material/Edit';
import Container from "@mui/material/Container"; import Container from "@mui/material/Container";
@ -7,24 +9,26 @@ import Card from "@mui/material/Card";
import Button from "@mui/material/Button"; import Button from "@mui/material/Button";
import {useTranslation} from "react-i18next"; import {useTranslation} from "react-i18next";
import session from "../app/Session"; import session from "../app/Session";
import {useEffect, useState} from "react"; import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline';
import theme from "./theme"; import theme from "./theme";
import {validUrl} from "../app/utils";
import Dialog from "@mui/material/Dialog"; import Dialog from "@mui/material/Dialog";
import DialogTitle from "@mui/material/DialogTitle"; import DialogTitle from "@mui/material/DialogTitle";
import DialogContent from "@mui/material/DialogContent"; import DialogContent from "@mui/material/DialogContent";
import TextField from "@mui/material/TextField"; import TextField from "@mui/material/TextField";
import DialogActions from "@mui/material/DialogActions"; import DialogActions from "@mui/material/DialogActions";
import userManager from "../app/UserManager";
import api from "../app/Api"; import api from "../app/Api";
import routes from "./routes"; import routes from "./routes";
import IconButton from "@mui/material/IconButton";
import {NavLink, useOutletContext} from "react-router-dom";
import Box from "@mui/material/Box";
const Account = () => { const Account = () => {
return ( return (
<Container maxWidth="md" sx={{marginTop: 3, marginBottom: 3}}> <Container maxWidth="md" sx={{marginTop: 3, marginBottom: 3}}>
<Stack spacing={3}> <Stack spacing={3}>
<Basics/> <Basics/>
<Stats/>
<Delete/>
</Stack> </Stack>
</Container> </Container>
); );
@ -38,14 +42,84 @@ const Basics = () => {
Account Account
</Typography> </Typography>
<PrefGroup> <PrefGroup>
<Pref labelId={"username"} title={"Username"}>{session.username()}</Pref> <Username/>
<ChangePassword/> <ChangePassword/>
</PrefGroup>
</Card>
);
};
const Stats = () => {
const { t } = useTranslation();
const { account } = useOutletContext();
return (
<Card sx={{p: 3}} aria-label={t("xxxxxxxxx")}>
<Typography variant="h5" sx={{marginBottom: 2}}>
{t("Usage")}
</Typography>
<PrefGroup>
<Pref labelId={"accountType"} title={t("Account type")}>
<div>
{account?.role === "admin"
? <>Unlimited <Tooltip title={"You are Admin"}><span style={{cursor: "default"}}>👑</span></Tooltip></>
: "Free"}
</div>
</Pref>
<Pref labelId={"dailyMessages"} title={t("Daily messages")}>
<div>
<Typography variant="body2" sx={{float: "left"}}>123</Typography>
<Typography variant="body2" sx={{float: "right"}}>of 1000</Typography>
</div>
<LinearProgress variant="determinate" value={10} />
</Pref>
<Pref labelId={"attachmentStorage"} title={t("Attachment storage")}>
<div>
<Typography variant="body2" sx={{float: "left"}}>15 MB used</Typography>
<Typography variant="body2" sx={{float: "right"}}>of 150 MB</Typography>
</div>
<LinearProgress variant="determinate" value={40} />
</Pref>
<Pref labelId={"emailLimits"} title={t("Emails sent")}>
<div>
<Typography variant="body2" sx={{float: "left"}}>2</Typography>
<Typography variant="body2" sx={{float: "right"}}>of 15</Typography>
</div>
<LinearProgress variant="determinate" value={20} />
</Pref>
</PrefGroup>
</Card>
);
};
const Delete = () => {
const { t } = useTranslation();
return (
<Card sx={{p: 3}} aria-label={t("xxxxxxxxx")}>
<Typography variant="h5" sx={{marginBottom: 2}}>
{t("Delete account")}
</Typography>
<PrefGroup>
<DeleteAccount/> <DeleteAccount/>
</PrefGroup> </PrefGroup>
</Card> </Card>
); );
}; };
const Username = () => {
const { t } = useTranslation();
const { account } = useOutletContext();
return (
<Pref labelId={"username"} title={t("Username")} description={t("Hey, that's you ❤")}>
<div>
{session.username()}
{account?.role === "admin"
? <>{" "}<Tooltip title={"You are Admin"}><span style={{cursor: "default"}}>👑</span></Tooltip></>
: ""}
</div>
</Pref>
)
};
const ChangePassword = () => { const ChangePassword = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const [dialogKey, setDialogKey] = useState(0); const [dialogKey, setDialogKey] = useState(0);
@ -69,10 +143,13 @@ const ChangePassword = () => {
} }
}; };
return ( return (
<Pref labelId={labelId} title={"Password"}> <Pref labelId={labelId} title={t("Password")} description={t("Change your account password")}>
<Button variant="outlined" startIcon={<EditIcon />} onClick={handleDialogOpen}> <div>
Change password <Typography color="gray" sx={{float: "left", fontSize: "0.7rem", lineHeight: "3.5"}}></Typography>
</Button> <IconButton onClick={handleDialogOpen} aria-label={t("xxxxxxxx")}>
<EditIcon/>
</IconButton>
</div>
<ChangePasswordDialog <ChangePasswordDialog
key={`changePasswordDialog${dialogKey}`} key={`changePasswordDialog${dialogKey}`}
open={dialogOpen} open={dialogOpen}
@ -152,10 +229,12 @@ const DeleteAccount = () => {
} }
}; };
return ( return (
<Pref labelId={labelId} title={t("Delete account")} description={t("This will permanently delete your account, including all data that is stored on the server.")}> <Pref labelId={labelId} title={t("Delete account")} description={t("Permanently delete your account")}>
<Button variant="outlined" startIcon={<EditIcon />} onClick={handleDialogOpen}> <div>
<Button fullWidth={false} variant="outlined" color="error" startIcon={<DeleteOutlineIcon />} onClick={handleDialogOpen}>
Delete account Delete account
</Button> </Button>
</div>
<DeleteAccountDialog <DeleteAccountDialog
key={`deleteAccountDialog${dialogKey}`} key={`deleteAccountDialog${dialogKey}`}
open={dialogOpen} open={dialogOpen}
@ -176,12 +255,12 @@ const DeleteAccountDialog = (props) => {
<DialogTitle>{t("Delete account")}</DialogTitle> <DialogTitle>{t("Delete account")}</DialogTitle>
<DialogContent> <DialogContent>
<Typography variant="body1"> <Typography variant="body1">
{t("This will permanently delete your account, including all data that is stored on the server. If you really want to proceed, please type {{username}} in the text box below.")} {t("This will permanently delete your account, including all data that is stored on the server. If you really want to proceed, please type '{{username}}' in the text box below.", { username: session.username()})}
</Typography> </Typography>
<TextField <TextField
margin="dense" margin="dense"
id="account-delete-confirm" id="account-delete-confirm"
label={t("Type '{{username}}' to delete account")} label={t("Type '{{username}}' to delete account", { username: session.username()})}
aria-label={t("xxxx")} aria-label={t("xxxx")}
type="text" type="text"
value={username} value={username}

View file

@ -81,6 +81,7 @@ const Layout = () => {
const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false); const [mobileDrawerOpen, setMobileDrawerOpen] = useState(false);
const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted()); const [notificationsGranted, setNotificationsGranted] = useState(notifier.granted());
const [sendDialogOpenMode, setSendDialogOpenMode] = useState(""); const [sendDialogOpenMode, setSendDialogOpenMode] = useState("");
const [account, setAccount] = useState(null);
const users = useLiveQuery(() => userManager.all()); const users = useLiveQuery(() => userManager.all());
const subscriptions = useLiveQuery(() => subscriptionManager.all()); const subscriptions = useLiveQuery(() => subscriptionManager.all());
const newNotificationsCount = subscriptions?.reduce((prev, cur) => prev + cur.new, 0) || 0; const newNotificationsCount = subscriptions?.reduce((prev, cur) => prev + cur.new, 0) || 0;
@ -95,24 +96,25 @@ const Layout = () => {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const account = await api.getAccountSettings("http://localhost:2586", session.token()); const acc = await api.getAccountSettings("http://localhost:2586", session.token());
if (account) { if (acc) {
if (account.language) { setAccount(acc);
await i18n.changeLanguage(account.language); if (acc.language) {
await i18n.changeLanguage(acc.language);
} }
if (account.notification) { if (acc.notification) {
if (account.notification.sound) { if (acc.notification.sound) {
await prefs.setSound(account.notification.sound); await prefs.setSound(acc.notification.sound);
} }
if (account.notification.delete_after) { if (acc.notification.delete_after) {
await prefs.setDeleteAfter(account.notification.delete_after); await prefs.setDeleteAfter(acc.notification.delete_after);
} }
if (account.notification.min_priority) { if (acc.notification.min_priority) {
await prefs.setMinPriority(account.notification.min_priority); await prefs.setMinPriority(acc.notification.min_priority);
} }
} }
if (account.subscriptions) { if (acc.subscriptions) {
await subscriptionManager.syncFromRemote(account.subscriptions); await subscriptionManager.syncFromRemote(acc.subscriptions);
} }
} }
})(); })();
@ -135,7 +137,7 @@ const Layout = () => {
/> />
<Main> <Main>
<Toolbar/> <Toolbar/>
<Outlet context={{ subscriptions, selected }}/> <Outlet context={{ account, subscriptions, selected }}/>
</Main> </Main>
<Messaging <Messaging
selected={selected} selected={selected}