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

View File

@ -1,13 +1,10 @@
import {ReactElement, useState} from "react"
import {AxiosError} from "axios"
import {List} from "@mui/material"
import {useQuery} from "@tanstack/react-query"
import {AliasList, PaginationResult} from "~/server-types"
import AliasListItem from "~/route-widgets/AliasesRoute/AliasListItem"
import CreateAliasButton from "~/route-widgets/AliasesRoute/CreateAliasButton"
import CustomAliasDialog from "~/route-widgets/AliasesRoute/CustomAliasDialog"
import AliasesDetails from "~/route-widgets/AliasesRoute/AliasesDetails"
import QueryResult from "~/components/QueryResult"
import SimplePage from "~/components/SimplePage"
import getAliases from "~/apis/get-aliases"
@ -22,34 +19,10 @@ export default function AliasesRoute(): ReactElement {
useState<boolean>(false)
return (
<>
<SimplePage
title="Aliases"
actions={
<CreateAliasButton
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)}
/>
</>
<SimplePage title="Aliases">
<QueryResult<PaginationResult<AliasList>> query={query}>
{result => <AliasesDetails aliases={result.items} />}
</QueryResult>
</SimplePage>
)
}