2022-12-16 04:07:04 +01:00
import * as React from 'react' ;
2022-12-17 19:49:32 +01:00
import { useState } from 'react' ;
import { LinearProgress , Stack , useMediaQuery } from "@mui/material" ;
import Tooltip from '@mui/material/Tooltip' ;
2022-12-16 04:07:04 +01:00
import Typography from "@mui/material/Typography" ;
import EditIcon from '@mui/icons-material/Edit' ;
import Container from "@mui/material/Container" ;
import Card from "@mui/material/Card" ;
import Button from "@mui/material/Button" ;
import { useTranslation } from "react-i18next" ;
import session from "../app/Session" ;
2022-12-17 19:49:32 +01:00
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutline' ;
2022-12-16 04:07:04 +01:00
import theme from "./theme" ;
import Dialog from "@mui/material/Dialog" ;
import DialogTitle from "@mui/material/DialogTitle" ;
import DialogContent from "@mui/material/DialogContent" ;
import TextField from "@mui/material/TextField" ;
import DialogActions from "@mui/material/DialogActions" ;
import routes from "./routes" ;
2022-12-17 19:49:32 +01:00
import IconButton from "@mui/material/IconButton" ;
2022-12-25 17:59:44 +01:00
import { useOutletContext } from "react-router-dom" ;
2022-12-19 15:59:32 +01:00
import { formatBytes } from "../app/utils" ;
2022-12-25 17:59:44 +01:00
import accountApi , { UnauthorizedError } from "../app/AccountApi" ;
2022-12-16 04:07:04 +01:00
const Account = ( ) => {
2022-12-19 15:59:32 +01:00
if ( ! session . exists ( ) ) {
window . location . href = routes . app ;
return < > < / > ;
}
2022-12-16 04:07:04 +01:00
return (
< Container maxWidth = "md" sx = { { marginTop : 3 , marginBottom : 3 } } >
< Stack spacing = { 3 } >
< Basics / >
2022-12-17 19:49:32 +01:00
< Stats / >
< Delete / >
2022-12-16 04:07:04 +01:00
< / S t a c k >
< / C o n t a i n e r >
) ;
} ;
const Basics = ( ) => {
const { t } = useTranslation ( ) ;
return (
< Card sx = { { p : 3 } } aria - label = { t ( "xxxxxxxxx" ) } >
< Typography variant = "h5" sx = { { marginBottom : 2 } } >
Account
< / T y p o g r a p h y >
< PrefGroup >
2022-12-17 19:49:32 +01:00
< Username / >
2022-12-16 04:07:04 +01:00
< ChangePassword / >
2022-12-17 19:49:32 +01:00
< / P r e f G r o u p >
< / C a r d >
) ;
} ;
const Stats = ( ) => {
const { t } = useTranslation ( ) ;
const { account } = useOutletContext ( ) ;
2022-12-19 15:59:32 +01:00
if ( ! account ) {
2022-12-28 21:51:09 +01:00
return < > < / > ;
2022-12-19 15:59:32 +01:00
}
const accountType = account . plan . code ? ? "none" ;
const normalize = ( value , max ) => ( value / max * 100 ) ;
2022-12-17 19:49:32 +01:00
return (
< Card sx = { { p : 3 } } aria - label = { t ( "xxxxxxxxx" ) } >
< Typography variant = "h5" sx = { { marginBottom : 2 } } >
{ t ( "Usage" ) }
< / T y p o g r a p h y >
< PrefGroup >
< Pref labelId = { "accountType" } title = { t ( "Account type" ) } >
< div >
{ account ? . role === "admin"
? < > Unlimited < Tooltip title = { "You are Admin" } > < span style = { { cursor : "default" } } > 👑 < / s p a n > < / T o o l t i p > < / >
2022-12-18 20:35:05 +01:00
: t ( ` account_type_ ${ accountType } ` ) }
2022-12-17 19:49:32 +01:00
< / d i v >
< / P r e f >
2022-12-19 15:59:32 +01:00
< Pref labelId = { "messages" } title = { t ( "Published messages" ) } >
2022-12-17 19:49:32 +01:00
< div >
2022-12-19 22:22:13 +01:00
< Typography variant = "body2" sx = { { float : "left" } } > { account . stats . messages } < / T y p o g r a p h y >
< Typography variant = "body2" sx = { { float : "right" } } > { account . limits . messages > 0 ? t ( "of {{limit}}" , { limit : account . limits . messages } ) : t ( "Unlimited" ) } < / T y p o g r a p h y >
2022-12-17 19:49:32 +01:00
< / d i v >
2022-12-19 22:22:13 +01:00
< LinearProgress variant = "determinate" value = { account . limits . messages > 0 ? normalize ( account . stats . messages , account . limits . messages ) : 100 } / >
2022-12-17 19:49:32 +01:00
< / P r e f >
2022-12-19 15:59:32 +01:00
< Pref labelId = { "emails" } title = { t ( "Emails sent" ) } >
2022-12-17 19:49:32 +01:00
< div >
2022-12-19 22:22:13 +01:00
< Typography variant = "body2" sx = { { float : "left" } } > { account . stats . emails } < / T y p o g r a p h y >
< Typography variant = "body2" sx = { { float : "right" } } > { account . limits . emails > 0 ? t ( "of {{limit}}" , { limit : account . limits . emails } ) : t ( "Unlimited" ) } < / T y p o g r a p h y >
2022-12-17 19:49:32 +01:00
< / d i v >
2022-12-19 22:22:13 +01:00
< LinearProgress variant = "determinate" value = { account . limits . emails > 0 ? normalize ( account . stats . emails , account . limits . emails ) : 100 } / >
2022-12-17 19:49:32 +01:00
< / P r e f >
2022-12-27 03:27:07 +01:00
< Pref labelId = { "attachments" } title = { t ( "Attachment storage" ) } subtitle = { t ( "{{filesize}} per file" , { filesize : formatBytes ( account . limits . attachment _file _size ) } ) } >
2022-12-17 19:49:32 +01:00
< div >
2022-12-19 22:22:13 +01:00
< Typography variant = "body2" sx = { { float : "left" } } > { formatBytes ( account . stats . attachment _total _size ) } < / T y p o g r a p h y >
< Typography variant = "body2" sx = { { float : "right" } } > { account . limits . attachment _total _size > 0 ? t ( "of {{limit}}" , { limit : formatBytes ( account . limits . attachment _total _size ) } ) : t ( "Unlimited" ) } < / T y p o g r a p h y >
2022-12-17 19:49:32 +01:00
< / d i v >
2022-12-19 22:22:13 +01:00
< LinearProgress variant = "determinate" value = { account . limits . attachment _total _size > 0 ? normalize ( account . stats . attachment _total _size , account . limits . attachment _total _size ) : 100 } / >
2022-12-17 19:49:32 +01:00
< / P r e f >
< / P r e f G r o u p >
2022-12-20 04:19:44 +01:00
{ account . limits . basis === "ip" && < Typography variant = "body1" >
< em > Usage stats and limits for this account are based on your IP address , so they may be shared
with other users . < / e m >
< / T y p o g r a p h y > }
2022-12-17 19:49:32 +01:00
< / C a r d >
) ;
} ;
const Delete = ( ) => {
const { t } = useTranslation ( ) ;
return (
< Card sx = { { p : 3 } } aria - label = { t ( "xxxxxxxxx" ) } >
< Typography variant = "h5" sx = { { marginBottom : 2 } } >
{ t ( "Delete account" ) }
< / T y p o g r a p h y >
< PrefGroup >
2022-12-16 04:07:04 +01:00
< DeleteAccount / >
< / P r e f G r o u p >
< / C a r d >
) ;
} ;
2022-12-17 19:49:32 +01:00
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" } } > 👑 < / s p a n > < / T o o l t i p > < / >
: "" }
< / d i v >
< / P r e f >
)
} ;
2022-12-16 04:07:04 +01:00
const ChangePassword = ( ) => {
const { t } = useTranslation ( ) ;
const [ dialogKey , setDialogKey ] = useState ( 0 ) ;
const [ dialogOpen , setDialogOpen ] = useState ( false ) ;
const labelId = "prefChangePassword" ;
const handleDialogOpen = ( ) => {
setDialogKey ( prev => prev + 1 ) ;
setDialogOpen ( true ) ;
} ;
const handleDialogCancel = ( ) => {
setDialogOpen ( false ) ;
} ;
const handleDialogSubmit = async ( newPassword ) => {
try {
2022-12-25 17:59:44 +01:00
await accountApi . changePassword ( newPassword ) ;
2022-12-16 04:07:04 +01:00
setDialogOpen ( false ) ;
console . debug ( ` [Account] Password changed ` ) ;
} catch ( e ) {
console . log ( ` [Account] Error changing password ` , e ) ;
2022-12-24 21:51:22 +01:00
if ( ( e instanceof UnauthorizedError ) ) {
2022-12-27 03:27:07 +01:00
session . resetAndRedirect ( routes . login ) ;
2022-12-24 21:51:22 +01:00
}
2022-12-16 04:07:04 +01:00
// TODO show error
}
} ;
return (
2022-12-17 19:49:32 +01:00
< Pref labelId = { labelId } title = { t ( "Password" ) } description = { t ( "Change your account password" ) } >
< div >
< Typography color = "gray" sx = { { float : "left" , fontSize : "0.7rem" , lineHeight : "3.5" } } > ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ ⬤ < / T y p o g r a p h y >
< IconButton onClick = { handleDialogOpen } aria - label = { t ( "xxxxxxxx" ) } >
< EditIcon / >
< / I c o n B u t t o n >
< / d i v >
2022-12-16 04:07:04 +01:00
< ChangePasswordDialog
key = { ` changePasswordDialog ${ dialogKey } ` }
open = { dialogOpen }
onCancel = { handleDialogCancel }
onSubmit = { handleDialogSubmit }
/ >
< / P r e f >
)
} ;
const ChangePasswordDialog = ( props ) => {
const { t } = useTranslation ( ) ;
const [ newPassword , setNewPassword ] = useState ( "" ) ;
const [ confirmPassword , setConfirmPassword ] = useState ( "" ) ;
const fullScreen = useMediaQuery ( theme . breakpoints . down ( 'sm' ) ) ;
const changeButtonEnabled = ( ( ) => {
return newPassword . length > 0 && newPassword === confirmPassword ;
} ) ( ) ;
return (
< Dialog open = { props . open } onClose = { props . onCancel } fullScreen = { fullScreen } >
< DialogTitle > Change password < / D i a l o g T i t l e >
< DialogContent >
< TextField
margin = "dense"
id = "new-password"
label = { t ( "New password" ) }
aria - label = { t ( "xxxx" ) }
type = "password"
value = { newPassword }
onChange = { ev => setNewPassword ( ev . target . value ) }
fullWidth
variant = "standard"
/ >
< TextField
margin = "dense"
id = "confirm"
label = { t ( "Confirm password" ) }
aria - label = { t ( "xxx" ) }
type = "password"
value = { confirmPassword }
onChange = { ev => setConfirmPassword ( ev . target . value ) }
fullWidth
variant = "standard"
/ >
< / D i a l o g C o n t e n t >
< DialogActions >
< Button onClick = { props . onCancel } > { t ( "Cancel" ) } < / B u t t o n >
< Button onClick = { ( ) => props . onSubmit ( newPassword ) } disabled = { ! changeButtonEnabled } > { t ( "Change password" ) } < / B u t t o n >
< / D i a l o g A c t i o n s >
< / D i a l o g >
) ;
} ;
const DeleteAccount = ( ) => {
const { t } = useTranslation ( ) ;
const [ dialogKey , setDialogKey ] = useState ( 0 ) ;
const [ dialogOpen , setDialogOpen ] = useState ( false ) ;
const labelId = "prefDeleteAccount" ;
const handleDialogOpen = ( ) => {
setDialogKey ( prev => prev + 1 ) ;
setDialogOpen ( true ) ;
} ;
const handleDialogCancel = ( ) => {
setDialogOpen ( false ) ;
} ;
const handleDialogSubmit = async ( newPassword ) => {
try {
2022-12-25 17:59:44 +01:00
await accountApi . delete ( ) ;
2022-12-28 21:51:09 +01:00
await db . delete ( ) ;
2022-12-16 04:07:04 +01:00
setDialogOpen ( false ) ;
console . debug ( ` [Account] Account deleted ` ) ;
2022-12-27 03:27:07 +01:00
session . resetAndRedirect ( routes . app ) ;
2022-12-16 04:07:04 +01:00
} catch ( e ) {
console . log ( ` [Account] Error deleting account ` , e ) ;
2022-12-24 21:51:22 +01:00
if ( ( e instanceof UnauthorizedError ) ) {
2022-12-27 03:27:07 +01:00
session . resetAndRedirect ( routes . login ) ;
2022-12-24 21:51:22 +01:00
}
2022-12-16 04:07:04 +01:00
// TODO show error
}
} ;
return (
2022-12-17 19:49:32 +01:00
< Pref labelId = { labelId } title = { t ( "Delete account" ) } description = { t ( "Permanently delete your account" ) } >
< div >
< Button fullWidth = { false } variant = "outlined" color = "error" startIcon = { < DeleteOutlineIcon / > } onClick = { handleDialogOpen } >
Delete account
< / B u t t o n >
< / d i v >
2022-12-16 04:07:04 +01:00
< DeleteAccountDialog
key = { ` deleteAccountDialog ${ dialogKey } ` }
open = { dialogOpen }
onCancel = { handleDialogCancel }
onSubmit = { handleDialogSubmit }
/ >
< / P r e f >
)
} ;
const DeleteAccountDialog = ( props ) => {
const { t } = useTranslation ( ) ;
const [ username , setUsername ] = useState ( "" ) ;
const fullScreen = useMediaQuery ( theme . breakpoints . down ( 'sm' ) ) ;
const buttonEnabled = username === session . username ( ) ;
return (
< Dialog open = { props . open } onClose = { props . onCancel } fullScreen = { fullScreen } >
< DialogTitle > { t ( "Delete account" ) } < / D i a l o g T i t l e >
< DialogContent >
< Typography variant = "body1" >
2022-12-17 19:49:32 +01:00
{ 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 ( ) } ) }
2022-12-16 04:07:04 +01:00
< / T y p o g r a p h y >
< TextField
margin = "dense"
id = "account-delete-confirm"
2022-12-17 19:49:32 +01:00
label = { t ( "Type '{{username}}' to delete account" , { username : session . username ( ) } ) }
2022-12-16 04:07:04 +01:00
aria - label = { t ( "xxxx" ) }
type = "text"
value = { username }
onChange = { ev => setUsername ( ev . target . value ) }
fullWidth
variant = "standard"
/ >
< / D i a l o g C o n t e n t >
< DialogActions >
< Button onClick = { props . onCancel } > { t ( "prefs_users_dialog_button_cancel" ) } < / B u t t o n >
< Button onClick = { props . onSubmit } color = "error" disabled = { ! buttonEnabled } > { t ( "Permanently delete account" ) } < / B u t t o n >
< / D i a l o g A c t i o n s >
< / D i a l o g >
) ;
} ;
// FIXME duplicate code
const PrefGroup = ( props ) => {
return (
< div role = "table" >
{ props . children }
< / d i v >
)
} ;
const Pref = ( props ) => {
return (
< div
role = "row"
style = { {
display : "flex" ,
flexDirection : "row" ,
marginTop : "10px" ,
marginBottom : "20px" ,
} }
>
< div
role = "cell"
id = { props . labelId }
aria - label = { props . title }
style = { {
flex : '1 0 40%' ,
display : 'flex' ,
flexDirection : 'column' ,
justifyContent : 'center' ,
paddingRight : '30px'
} }
>
2022-12-20 04:19:44 +01:00
< div > < b > { props . title } < / b > { p r o p s . s u b t i t l e & & < e m > ( { p r o p s . s u b t i t l e } ) < / e m > } < / d i v >
2022-12-16 04:07:04 +01:00
{ props . description && < div > < em > { props . description } < / e m > < / d i v > }
< / d i v >
< div
role = "cell"
style = { {
flex : '1 0 calc(60% - 50px)' ,
display : 'flex' ,
flexDirection : 'column' ,
justifyContent : 'center'
} }
>
{ props . children }
< / d i v >
< / d i v >
) ;
} ;
export default Account ;