mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-20 00:05:26 +02:00
improved AliasesRoute
This commit is contained in:
parent
c6634dc740
commit
89c7c5db2b
@ -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>
|
||||
)
|
||||
}
|
59
src/route-widgets/AliasesRoute/AliasesDetails.tsx
Normal file
59
src/route-widgets/AliasesRoute/AliasesDetails.tsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
@ -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!"}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user