improved AliasesRoute

This commit is contained in:
Myzel394 2022-10-30 18:59:13 +01:00
parent c6634dc740
commit 89c7c5db2b
5 changed files with 176 additions and 176 deletions

View File

@ -1,31 +0,0 @@
import {ReactElement} from "react"
import {FaHashtag, FaRandom} from "react-icons/fa"
import {ListItemButton, ListItemIcon, ListItemText} from "@mui/material"
import {AliasList, AliasType} from "~/server-types"
import AliasTypeIndicator from "~/components/AliasTypeIndicator"
export interface AliasListItemProps {
alias: AliasList
}
const ALIAS_TYPE_ICON_MAP: Record<AliasType, ReactElement> = {
[AliasType.RANDOM]: <FaRandom />,
[AliasType.CUSTOM]: <FaHashtag />,
}
export default function AliasListItem({
alias,
}: AliasListItemProps): ReactElement {
const address = `${alias.local}@${alias.domain}`
return (
<ListItemButton href={`/aliases/${btoa(address)}`}>
<ListItemIcon>
<AliasTypeIndicator type={alias.type} />
</ListItemIcon>
<ListItemText primary={address} />
</ListItemButton>
)
}

View File

@ -0,0 +1,59 @@
import {ReactElement} from "react"
import {
Grid,
List,
ListItemButton,
ListItemIcon,
ListItemText,
} from "@mui/material"
import {AliasTypeIndicator} from "~/components"
import {AliasList} from "~/server-types"
import {useUIState} from "~/hooks"
import CreateAliasButton from "~/route-widgets/AliasesRoute/CreateAliasButton"
export interface AliasesDetailsProps {
aliases: AliasList[]
}
const getAddress = (alias: AliasList): string =>
`${alias.local}@${alias.domain}`
export default function AliasesDetails({
aliases,
}: AliasesDetailsProps): ReactElement {
const [aliasesUIState, setAliasesUIState] = useUIState<AliasList[]>(aliases)
return (
<>
<Grid container spacing={4} direction="column">
<Grid item>
<List>
{aliasesUIState.map(alias => (
<ListItemButton
key={alias.id}
href={`/aliases/${btoa(getAddress(alias))}`}
>
<ListItemIcon>
<AliasTypeIndicator type={alias.type} />
</ListItemIcon>
<ListItemText primary={getAddress(alias)} />
</ListItemButton>
))}
</List>
</Grid>
<Grid item>
<CreateAliasButton
onCreated={alias =>
setAliasesUIState(currentAliases => [
alias,
...currentAliases,
])
}
/>
</Grid>
</Grid>
</>
)
}

View File

@ -15,27 +15,32 @@ import {
} from "@mui/material" } from "@mui/material"
import {useMutation} from "@tanstack/react-query" import {useMutation} from "@tanstack/react-query"
import {createAlias} from "~/apis" import {CreateAliasData, createAlias} from "~/apis"
import {Alias, AliasType} from "~/server-types" import {Alias, AliasType} from "~/server-types"
import {parseFastAPIError} from "~/utils"
import {ErrorSnack, SuccessSnack} from "~/components"
import CustomAliasDialog from "~/route-widgets/AliasesRoute/CustomAliasDialog"
export interface CreateAliasButtonProps { export interface CreateAliasButtonProps {
onRandomCreated: (alias: Alias) => void onCreated: (alias: Alias) => void
onCustomCreated: () => void
} }
export default function CreateAliasButton({ export default function CreateAliasButton({
onRandomCreated, onCreated,
onCustomCreated,
}: CreateAliasButtonProps): ReactElement { }: CreateAliasButtonProps): ReactElement {
const {mutate, isLoading} = useMutation<Alias, AxiosError, void>( const [errorMessage, setErrorMessage] = useState<string>("")
() => const {mutateAsync, isLoading, isSuccess} = useMutation<
createAlias({ Alias,
type: AliasType.RANDOM, AxiosError,
}), CreateAliasData
{ >(values => createAlias(values), {
onSuccess: onRandomCreated, onSuccess: onCreated,
}, onError: error =>
) setErrorMessage(parseFastAPIError(error).detail as string),
})
const [showCustomCreateDialog, setShowCustomCreateDialog] =
useState<boolean>(false)
const [anchorElement, setAnchorElement] = useState<HTMLElement | null>(null) const [anchorElement, setAnchorElement] = useState<HTMLElement | null>(null)
const open = Boolean(anchorElement) const open = Boolean(anchorElement)
@ -46,7 +51,11 @@ export default function CreateAliasButton({
<Button <Button
disabled={isLoading} disabled={isLoading}
startIcon={<BsArrowClockwise />} startIcon={<BsArrowClockwise />}
onClick={() => mutate()} onClick={() =>
mutateAsync({
type: AliasType.RANDOM,
})
}
> >
Create random alias Create random alias
</Button> </Button>
@ -64,9 +73,10 @@ export default function CreateAliasButton({
> >
<MenuList> <MenuList>
<MenuItem <MenuItem
disabled={isLoading}
onClick={() => { onClick={() => {
setShowCustomCreateDialog(true)
setAnchorElement(null) setAnchorElement(null)
onCustomCreated()
}} }}
> >
<ListItemIcon> <ListItemIcon>
@ -76,6 +86,19 @@ export default function CreateAliasButton({
</MenuItem> </MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
<CustomAliasDialog
visible={showCustomCreateDialog}
isLoading={isLoading}
onCreate={async values => {
await mutateAsync(values)
setShowCustomCreateDialog(false)
}}
onClose={() => setShowCustomCreateDialog(false)}
/>
<ErrorSnack message={errorMessage} />
<SuccessSnack
message={isSuccess && "Created Alias successfully!"}
/>
</> </>
) )
} }

View File

@ -18,17 +18,16 @@ import {
TextField, TextField,
Typography, Typography,
} from "@mui/material" } from "@mui/material"
import {useMutation} from "@tanstack/react-query"
import {Alias, AliasType, ServerSettings} from "~/server-types" import {AliasType, ServerSettings} from "~/server-types"
import {CreateAliasData, createAlias} from "~/apis" import {CreateAliasData} from "~/apis"
import {parseFastAPIError} from "~/utils" import {parseFastAPIError} from "~/utils"
import {LOCAL_REGEX} from "~/constants/values" import {LOCAL_REGEX} from "~/constants/values"
import {ErrorSnack, SuccessSnack} from "~/components"
export interface CustomAliasDialogProps { export interface CustomAliasDialogProps {
visible: boolean visible: boolean
onCreated: () => void onCreate: (values: CreateAliasData) => void
isLoading: boolean
onClose: () => void onClose: () => void
} }
@ -40,7 +39,8 @@ interface Form {
export default function CustomAliasDialog({ export default function CustomAliasDialog({
visible, visible,
onCreated, isLoading,
onCreate,
onClose, onClose,
}: CustomAliasDialogProps): ReactElement { }: CustomAliasDialogProps): ReactElement {
const serverSettings = useLoaderData() as ServerSettings const serverSettings = useLoaderData() as ServerSettings
@ -54,25 +54,6 @@ export default function CustomAliasDialog({
.max(64 - serverSettings.customAliasSuffixLength - 1), .max(64 - serverSettings.customAliasSuffixLength - 1),
}) })
const {mutateAsync, isLoading, isSuccess, reset} = useMutation<
Alias,
AxiosError,
Omit<CreateAliasData, "type">
>(
values =>
// @ts-ignore
createAlias({
type: AliasType.CUSTOM,
...values,
}),
{
onSuccess: () => {
reset()
onCreated()
},
},
)
const formik = useFormik<Form>({ const formik = useFormik<Form>({
validationSchema: schema, validationSchema: schema,
initialValues: { initialValues: {
@ -80,9 +61,11 @@ export default function CustomAliasDialog({
}, },
onSubmit: async (values, {setErrors}) => { onSubmit: async (values, {setErrors}) => {
try { try {
await mutateAsync({ await onCreate({
local: values.local, local: values.local,
type: AliasType.CUSTOM,
}) })
formik.resetForm()
} catch (error) { } catch (error) {
setErrors(parseFastAPIError(error as AxiosError)) setErrors(parseFastAPIError(error as AxiosError))
} }
@ -90,75 +73,68 @@ export default function CustomAliasDialog({
}) })
return ( return (
<> <Dialog onClose={onClose} open={visible} keepMounted={false}>
<Dialog onClose={onClose} open={visible} keepMounted={false}> <form onSubmit={formik.handleSubmit}>
<form onSubmit={formik.handleSubmit}> <DialogTitle>Create Custom Alias</DialogTitle>
<DialogTitle>Create Custom Alias</DialogTitle> <DialogContent>
<DialogContent> <DialogContentText>
<DialogContentText> You can define your own custom alias. Note that a random
You can define your own custom alias. Note that a suffix will be added at the end to avoid duplicates.
random suffix will be added at the end to avoid </DialogContentText>
duplicates. <Box paddingY={4}>
</DialogContentText> <TextField
<Box paddingY={4}> key="local"
<TextField fullWidth
key="local" autoFocus
fullWidth name="local"
autoFocus id="local"
name="local" label="Address"
id="local" value={formik.values.local}
label="Address" onChange={formik.handleChange}
value={formik.values.local} disabled={formik.isSubmitting}
onChange={formik.handleChange} error={
disabled={formik.isSubmitting} formik.touched.local &&
error={ Boolean(formik.errors.local)
formik.touched.local && }
Boolean(formik.errors.local) helperText={
} formik.touched.local && formik.errors.local
helperText={ }
formik.touched.local && formik.errors.local InputProps={{
} endAdornment: (
InputProps={{ <InputAdornment position="end">
endAdornment: ( <Typography variant="body2">
<InputAdornment position="end"> <span>
<Typography variant="body2"> {Array(
<span> serverSettings.customAliasSuffixLength,
{Array( )
serverSettings.customAliasSuffixLength, .fill("#")
) .join("")}
.fill("#") </span>
.join("")} <span>
</span> @{serverSettings.mailDomain}
<span> </span>
@{serverSettings.mailDomain} </Typography>
</span> </InputAdornment>
</Typography> ),
</InputAdornment> }}
), />
}} </Box>
/> </DialogContent>
</Box> <DialogActions>
</DialogContent> <Button onClick={onClose} startIcon={<TiCancel />}>
<DialogActions> Cancel
<Button onClick={onClose} startIcon={<TiCancel />}> </Button>
Cancel <Button
</Button> onClick={() => {}}
<Button disabled={isLoading}
onClick={() => {}} startIcon={<FaPen />}
disabled={isLoading} variant="contained"
startIcon={<FaPen />} type="submit"
variant="contained" >
type="submit" Create Alias
> </Button>
Create Alias </DialogActions>
</Button> </form>
</DialogActions> </Dialog>
</form>
</Dialog>
<ErrorSnack message={formik.errors.detail} />
<SuccessSnack
message={isSuccess && "Created Alias successfully!"}
/>
</>
) )
} }

View File

@ -1,13 +1,10 @@
import {ReactElement, useState} from "react" import {ReactElement, useState} from "react"
import {AxiosError} from "axios" import {AxiosError} from "axios"
import {List} from "@mui/material"
import {useQuery} from "@tanstack/react-query" import {useQuery} from "@tanstack/react-query"
import {AliasList, PaginationResult} from "~/server-types" import {AliasList, PaginationResult} from "~/server-types"
import AliasListItem from "~/route-widgets/AliasesRoute/AliasListItem" import AliasesDetails from "~/route-widgets/AliasesRoute/AliasesDetails"
import CreateAliasButton from "~/route-widgets/AliasesRoute/CreateAliasButton"
import CustomAliasDialog from "~/route-widgets/AliasesRoute/CustomAliasDialog"
import QueryResult from "~/components/QueryResult" import QueryResult from "~/components/QueryResult"
import SimplePage from "~/components/SimplePage" import SimplePage from "~/components/SimplePage"
import getAliases from "~/apis/get-aliases" import getAliases from "~/apis/get-aliases"
@ -22,34 +19,10 @@ export default function AliasesRoute(): ReactElement {
useState<boolean>(false) useState<boolean>(false)
return ( return (
<> <SimplePage title="Aliases">
<SimplePage <QueryResult<PaginationResult<AliasList>> query={query}>
title="Aliases" {result => <AliasesDetails aliases={result.items} />}
actions={ </QueryResult>
<CreateAliasButton </SimplePage>
onRandomCreated={() => query.refetch()}
onCustomCreated={() => setShowCustomCreateDialog(true)}
/>
}
>
<QueryResult<PaginationResult<AliasList>> query={query}>
{result => (
<List>
{result.items.map(alias => (
<AliasListItem key={alias.id} alias={alias} />
))}
</List>
)}
</QueryResult>
</SimplePage>
<CustomAliasDialog
visible={showCustomCreateDialog}
onCreated={() => {
setShowCustomCreateDialog(false)
query.refetch()
}}
onClose={() => setShowCustomCreateDialog(false)}
/>
</>
) )
} }