mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-20 08:15: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"
|
} 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!"}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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,15 +73,13 @@ 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
|
You can define your own custom alias. Note that a random
|
||||||
random suffix will be added at the end to avoid
|
suffix will be added at the end to avoid duplicates.
|
||||||
duplicates.
|
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<Box paddingY={4}>
|
<Box paddingY={4}>
|
||||||
<TextField
|
<TextField
|
||||||
@ -155,10 +136,5 @@ export default function CustomAliasDialog({
|
|||||||
</DialogActions>
|
</DialogActions>
|
||||||
</form>
|
</form>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
<ErrorSnack message={formik.errors.detail} />
|
|
||||||
<SuccessSnack
|
|
||||||
message={isSuccess && "Created Alias successfully!"}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
title="Aliases"
|
|
||||||
actions={
|
|
||||||
<CreateAliasButton
|
|
||||||
onRandomCreated={() => query.refetch()}
|
|
||||||
onCustomCreated={() => setShowCustomCreateDialog(true)}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<QueryResult<PaginationResult<AliasList>> query={query}>
|
<QueryResult<PaginationResult<AliasList>> query={query}>
|
||||||
{result => (
|
{result => <AliasesDetails aliases={result.items} />}
|
||||||
<List>
|
|
||||||
{result.items.map(alias => (
|
|
||||||
<AliasListItem key={alias.id} alias={alias} />
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
)}
|
|
||||||
</QueryResult>
|
</QueryResult>
|
||||||
</SimplePage>
|
</SimplePage>
|
||||||
<CustomAliasDialog
|
|
||||||
visible={showCustomCreateDialog}
|
|
||||||
onCreated={() => {
|
|
||||||
setShowCustomCreateDialog(false)
|
|
||||||
query.refetch()
|
|
||||||
}}
|
|
||||||
onClose={() => setShowCustomCreateDialog(false)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user