mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-20 00:05:26 +02:00
improvements; added CustomAliasDialog.tsx
This commit is contained in:
parent
ca9eb78330
commit
3c9918e8b7
@ -58,6 +58,7 @@ const router = createBrowserRouter([
|
|||||||
element: <AuthenticatedRoute />,
|
element: <AuthenticatedRoute />,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
|
loader: getServerSettings,
|
||||||
path: "/aliases",
|
path: "/aliases",
|
||||||
element: <AliasesRoute />,
|
element: <AliasesRoute />,
|
||||||
},
|
},
|
||||||
|
@ -1 +1,2 @@
|
|||||||
export const MASTER_PASSWORD_LENGTH = 4096
|
export const MASTER_PASSWORD_LENGTH = 4096
|
||||||
|
export const LOCAL_REGEX = /^[a-zA-Z0-9!#$%&‘*+–/=?^_`.{|}~-]{1,64}$/g
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import {ReactElement} from "react"
|
|
||||||
import {MdOutlineMoreVert} from "react-icons/md"
|
|
||||||
|
|
||||||
import {
|
|
||||||
IconButton,
|
|
||||||
ListItemButton,
|
|
||||||
ListItemSecondaryAction,
|
|
||||||
ListItemText,
|
|
||||||
} from "@mui/material"
|
|
||||||
|
|
||||||
import {AliasList} from "~/server-types"
|
|
||||||
|
|
||||||
export interface AliasListItemProps {
|
|
||||||
alias: AliasList
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function AliasListItem({
|
|
||||||
alias,
|
|
||||||
}: AliasListItemProps): ReactElement {
|
|
||||||
const address = `${alias.local}@${alias.domain}`
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListItemButton href={`/aliases/${btoa(address)}`}>
|
|
||||||
<ListItemText primary={address} />
|
|
||||||
<ListItemSecondaryAction>
|
|
||||||
<IconButton edge="end">
|
|
||||||
<MdOutlineMoreVert />
|
|
||||||
</IconButton>
|
|
||||||
</ListItemSecondaryAction>
|
|
||||||
</ListItemButton>
|
|
||||||
)
|
|
||||||
}
|
|
28
src/route-widgets/AliasesRoute/AliasListItem.tsx
Normal file
28
src/route-widgets/AliasesRoute/AliasListItem.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import {ReactElement} from "react"
|
||||||
|
import {FaHashtag, FaRandom} from "react-icons/fa"
|
||||||
|
|
||||||
|
import {ListItemButton, ListItemIcon, ListItemText} from "@mui/material"
|
||||||
|
|
||||||
|
import {AliasList, AliasType} from "~/server-types"
|
||||||
|
|
||||||
|
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>{ALIAS_TYPE_ICON_MAP[alias.type]}</ListItemIcon>
|
||||||
|
<ListItemText primary={address} />
|
||||||
|
</ListItemButton>
|
||||||
|
)
|
||||||
|
}
|
@ -15,20 +15,25 @@ import {
|
|||||||
} from "@mui/material"
|
} from "@mui/material"
|
||||||
import {useMutation} from "@tanstack/react-query"
|
import {useMutation} from "@tanstack/react-query"
|
||||||
|
|
||||||
import {CreateAliasData, createAlias} from "~/apis"
|
import {createAlias} from "~/apis"
|
||||||
import {Alias, AliasType} from "~/server-types"
|
import {Alias, AliasType} from "~/server-types"
|
||||||
|
|
||||||
export interface CreateRandomAliasButtonProps {
|
export interface CreateAliasButtonProps {
|
||||||
onCreated: (alias: Alias) => void
|
onRandomCreated: (alias: Alias) => void
|
||||||
|
onCustomCreated: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function CreateRandomAliasButton({
|
export default function CreateAliasButton({
|
||||||
onCreated,
|
onRandomCreated,
|
||||||
}: CreateRandomAliasButtonProps): ReactElement {
|
onCustomCreated,
|
||||||
const {mutate, isLoading} = useMutation<Alias, AxiosError, CreateAliasData>(
|
}: CreateAliasButtonProps): ReactElement {
|
||||||
createAlias,
|
const {mutate, isLoading} = useMutation<Alias, AxiosError, void>(
|
||||||
|
() =>
|
||||||
|
createAlias({
|
||||||
|
type: AliasType.RANDOM,
|
||||||
|
}),
|
||||||
{
|
{
|
||||||
onSuccess: onCreated,
|
onSuccess: onRandomCreated,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,11 +46,7 @@ export default function CreateRandomAliasButton({
|
|||||||
<Button
|
<Button
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
startIcon={<BsArrowClockwise />}
|
startIcon={<BsArrowClockwise />}
|
||||||
onClick={() =>
|
onClick={() => mutate()}
|
||||||
mutate({
|
|
||||||
type: AliasType.RANDOM,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
Create random alias
|
Create random alias
|
||||||
</Button>
|
</Button>
|
||||||
@ -62,7 +63,12 @@ export default function CreateRandomAliasButton({
|
|||||||
onClose={() => setAnchorElement(null)}
|
onClose={() => setAnchorElement(null)}
|
||||||
>
|
>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem>
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setAnchorElement(null)
|
||||||
|
onCustomCreated()
|
||||||
|
}}
|
||||||
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<FaPen />
|
<FaPen />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
164
src/route-widgets/AliasesRoute/CustomAliasDialog.tsx
Normal file
164
src/route-widgets/AliasesRoute/CustomAliasDialog.tsx
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
import * as yup from "yup"
|
||||||
|
import {ReactElement} from "react"
|
||||||
|
import {useFormik} from "formik"
|
||||||
|
import {useLoaderData} from "react-router-dom"
|
||||||
|
import {AxiosError} from "axios"
|
||||||
|
import {TiCancel} from "react-icons/ti"
|
||||||
|
import {FaPen} from "react-icons/fa"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogTitle,
|
||||||
|
InputAdornment,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material"
|
||||||
|
import {useMutation} from "@tanstack/react-query"
|
||||||
|
|
||||||
|
import {Alias, AliasType, ServerSettings} from "~/server-types"
|
||||||
|
import {CreateAliasData, createAlias} from "~/apis"
|
||||||
|
import {parseFastAPIError} from "~/utils"
|
||||||
|
import {LOCAL_REGEX} from "~/constants/values"
|
||||||
|
import {ErrorSnack, SuccessSnack} from "~/components"
|
||||||
|
|
||||||
|
export interface CustomAliasDialogProps {
|
||||||
|
visible: boolean
|
||||||
|
onCreated: () => void
|
||||||
|
onClose: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Form {
|
||||||
|
local: string
|
||||||
|
|
||||||
|
detail?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CustomAliasDialog({
|
||||||
|
visible,
|
||||||
|
onCreated,
|
||||||
|
onClose,
|
||||||
|
}: CustomAliasDialogProps): ReactElement {
|
||||||
|
const serverSettings = useLoaderData() as ServerSettings
|
||||||
|
|
||||||
|
const schema = yup.object().shape({
|
||||||
|
local: yup
|
||||||
|
.string()
|
||||||
|
.matches(LOCAL_REGEX)
|
||||||
|
.required()
|
||||||
|
.min(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>({
|
||||||
|
validationSchema: schema,
|
||||||
|
initialValues: {
|
||||||
|
local: "",
|
||||||
|
},
|
||||||
|
onSubmit: async (values, {setErrors}) => {
|
||||||
|
try {
|
||||||
|
await mutateAsync({
|
||||||
|
local: values.local,
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
setErrors(parseFastAPIError(error as AxiosError))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
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!"}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -95,7 +95,7 @@ export default function ConfirmCodeForm({
|
|||||||
fullWidth
|
fullWidth
|
||||||
name="code"
|
name="code"
|
||||||
id="code"
|
id="code"
|
||||||
label="code"
|
label="Code"
|
||||||
value={formik.values.code}
|
value={formik.values.code}
|
||||||
onChange={formik.handleChange}
|
onChange={formik.handleChange}
|
||||||
disabled={formik.isSubmitting}
|
disabled={formik.isSubmitting}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import {ReactElement} from "react"
|
import {ReactElement, useState} from "react"
|
||||||
import {AxiosError} from "axios"
|
import {AxiosError} from "axios"
|
||||||
|
|
||||||
import {Grid, List, Typography} from "@mui/material"
|
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/AliasRoute/AliasListItem"
|
import AliasListItem from "~/route-widgets/AliasesRoute/AliasListItem"
|
||||||
import CreateRandomAliasButton from "~/route-widgets/AliasRoute/CreateRandomAliasButton"
|
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"
|
||||||
@ -17,34 +18,38 @@ export default function AliasesRoute(): ReactElement {
|
|||||||
getAliases,
|
getAliases,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [showCustomCreateDialog, setShowCustomCreateDialog] =
|
||||||
|
useState<boolean>(false)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimplePage title="Aliases">
|
<>
|
||||||
<Grid container spacing={4} direction="column" alignItems="stretch">
|
<SimplePage
|
||||||
<Grid item>
|
title="Aliases"
|
||||||
<Typography variant="h6" component="h2">
|
actions={
|
||||||
Random Aliases
|
<CreateAliasButton
|
||||||
</Typography>
|
onRandomCreated={() => query.refetch()}
|
||||||
</Grid>
|
onCustomCreated={() => setShowCustomCreateDialog(true)}
|
||||||
<Grid item>
|
|
||||||
<QueryResult<PaginationResult<AliasList>> query={query}>
|
|
||||||
{result => (
|
|
||||||
<List>
|
|
||||||
{result.items.map(alias => (
|
|
||||||
<AliasListItem
|
|
||||||
key={alias.id}
|
|
||||||
alias={alias}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</List>
|
|
||||||
)}
|
|
||||||
</QueryResult>
|
|
||||||
</Grid>
|
|
||||||
<Grid item>
|
|
||||||
<CreateRandomAliasButton
|
|
||||||
onCreated={() => query.refetch()}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
}
|
||||||
</Grid>
|
>
|
||||||
</SimplePage>
|
<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)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -74,6 +74,7 @@ export interface ServerSettings {
|
|||||||
emailLoginTokenChars: string
|
emailLoginTokenChars: string
|
||||||
emailLoginTokenLength: number
|
emailLoginTokenLength: number
|
||||||
emailResendWaitTime: number
|
emailResendWaitTime: number
|
||||||
|
customAliasSuffixLength: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Alias {
|
export interface Alias {
|
||||||
@ -95,6 +96,7 @@ export interface AliasList {
|
|||||||
domain: string
|
domain: string
|
||||||
local: string
|
local: string
|
||||||
isActive: boolean
|
isActive: boolean
|
||||||
|
type: AliasType
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Report {
|
export interface Report {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user