mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-20 08:15:26 +02:00
added Reserved Alias creation
This commit is contained in:
parent
cd1fe2005a
commit
32f8d17418
@ -281,7 +281,28 @@
|
||||
"description": "We are logging you out..."
|
||||
},
|
||||
"AdminRoute": {
|
||||
"title": "Site configuration"
|
||||
"title": "Site configuration",
|
||||
"forms": {
|
||||
"reservedAliases": {
|
||||
"title": "Reserved Aliases",
|
||||
"description": "Define which aliases will be reserved for your domain.",
|
||||
"saveAction": "Create Alias",
|
||||
"fields": {
|
||||
"local": {
|
||||
"label": "Local"
|
||||
},
|
||||
"users": {
|
||||
"label": "Users",
|
||||
"me": "{{email}} (Me)"
|
||||
}
|
||||
},
|
||||
"explanation": {
|
||||
"step1": "User from outside",
|
||||
"step2": "Sends mail to",
|
||||
"step4": "KleckRelay forwards to"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -102,6 +102,7 @@ const router = createBrowserRouter([
|
||||
},
|
||||
{
|
||||
path: "/admin",
|
||||
loader: getServerSettings,
|
||||
element: <AdminRoute />,
|
||||
},
|
||||
],
|
||||
|
25
src/apis/create-reserved-alias.ts
Normal file
25
src/apis/create-reserved-alias.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import {ReservedAlias} from "~/server-types"
|
||||
import {client} from "~/constants/axios-client"
|
||||
|
||||
export interface CreateReservedAliasData {
|
||||
local: string
|
||||
users: Array<{
|
||||
id: string
|
||||
}>
|
||||
|
||||
isActive?: boolean
|
||||
}
|
||||
|
||||
export default async function createReservedAlias(
|
||||
aliasData: CreateReservedAliasData,
|
||||
): Promise<ReservedAlias> {
|
||||
const {data} = await client.post(
|
||||
`${import.meta.env.VITE_SERVER_BASE_URL}/v1/reserved-alias`,
|
||||
aliasData,
|
||||
{
|
||||
withCredentials: true,
|
||||
},
|
||||
)
|
||||
|
||||
return data
|
||||
}
|
@ -42,3 +42,5 @@ export * from "./get-admin-users"
|
||||
export {default as getAdminUsers} from "./get-admin-users"
|
||||
export * from "./get-reserved-aliases"
|
||||
export {default as getReservedAliases} from "./get-reserved-aliases"
|
||||
export * from "./create-reserved-alias"
|
||||
export {default as createReservedAlias} from "./create-reserved-alias"
|
||||
|
@ -73,7 +73,7 @@ export default function SimpleForm({
|
||||
<Grid item>
|
||||
<Grid container spacing={3} direction="column" alignItems="center">
|
||||
{children.map(input => (
|
||||
<Grid item key={input.key}>
|
||||
<Grid item key={input.key} width="100%">
|
||||
{input}
|
||||
</Grid>
|
||||
))}
|
||||
|
99
src/route-widgets/AdminPage/AliasExplanation.tsx
Normal file
99
src/route-widgets/AdminPage/AliasExplanation.tsx
Normal file
@ -0,0 +1,99 @@
|
||||
import {Grid, List, ListItem, ListItemText, Typography, useTheme} from "@mui/material"
|
||||
import {ReactElement} from "react"
|
||||
import {MdMail} from "react-icons/md"
|
||||
import {useLoaderData} from "react-router"
|
||||
import {ServerSettings} from "~/server-types"
|
||||
import {useTranslation} from "react-i18next"
|
||||
import {BsArrowDown} from "react-icons/bs"
|
||||
import {FaMask} from "react-icons/fa"
|
||||
import {HiUsers} from "react-icons/hi"
|
||||
|
||||
export interface AliasExplanationProps {
|
||||
local: string
|
||||
emails: string[]
|
||||
}
|
||||
|
||||
export default function AliasExplanation({local, emails}: AliasExplanationProps): ReactElement {
|
||||
const {t} = useTranslation()
|
||||
const theme = useTheme()
|
||||
const serverSettings = useLoaderData() as ServerSettings
|
||||
|
||||
return (
|
||||
<Grid
|
||||
container
|
||||
direction="column"
|
||||
padding={4}
|
||||
gap={4}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
border={1}
|
||||
borderColor={theme.palette.text.disabled}
|
||||
bgcolor={theme.palette.background.default}
|
||||
>
|
||||
<Grid item>
|
||||
<Grid container direction="column" spacing={1} alignItems="center">
|
||||
<Grid item>
|
||||
<MdMail size={24} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="caption" align="center">
|
||||
{t("routes.AdminRoute.forms.reservedAliases.explanation.step1")}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid container direction="column" spacing={1} alignItems="center">
|
||||
<Grid item>
|
||||
<BsArrowDown size={24} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="caption" align="center">
|
||||
{t("routes.AdminRoute.forms.reservedAliases.explanation.step2")}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid container direction="column" spacing={1} alignItems="center">
|
||||
<Grid item>
|
||||
<FaMask size={24} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="body1" align="center">
|
||||
<span style={{display: "block"}}>{local}</span>
|
||||
<span style={{opacity: 0.4}}>@{serverSettings.mailDomain}</span>
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid container direction="column" spacing={1} alignItems="center">
|
||||
<Grid item>
|
||||
<BsArrowDown size={24} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Typography variant="caption" align="center">
|
||||
{t("routes.AdminRoute.forms.reservedAliases.explanation.step4")}
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<Grid container direction="column" spacing={1} alignItems="center">
|
||||
<Grid item>
|
||||
<HiUsers size={24} />
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<List dense>
|
||||
{emails.map(email => (
|
||||
<ListItem key={email}>
|
||||
<ListItemText primary={email} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
@ -1,17 +1,182 @@
|
||||
import * as yup from "yup"
|
||||
import {ReactElement} from "react"
|
||||
import {AxiosError} from "axios"
|
||||
import {useTranslation} from "react-i18next"
|
||||
import {useFormik} from "formik"
|
||||
|
||||
import {useQuery} from "@tanstack/react-query"
|
||||
import {useMutation, useQuery} from "@tanstack/react-query"
|
||||
|
||||
import {GetAdminUsersResponse, getAdminUsers} from "~/apis"
|
||||
import {
|
||||
CreateReservedAliasData,
|
||||
GetAdminUsersResponse,
|
||||
createReservedAlias,
|
||||
getAdminUsers,
|
||||
} from "~/apis"
|
||||
import {Grid, InputAdornment, MenuItem, TextField} from "@mui/material"
|
||||
import {BiText} from "react-icons/bi"
|
||||
import {HiUsers} from "react-icons/hi"
|
||||
import {useErrorSuccessSnacks, useNavigateToNext, useUser} from "~/hooks"
|
||||
import {ReservedAlias, ServerUser} from "~/server-types"
|
||||
import {parseFastAPIError} from "~/utils"
|
||||
import {SimpleForm} from "~/components"
|
||||
import AliasExplanation from "~/route-widgets/AdminPage/AliasExplanation"
|
||||
|
||||
interface Form {
|
||||
local: string
|
||||
users: string[]
|
||||
|
||||
isActive?: boolean
|
||||
|
||||
nonFieldError?: string
|
||||
}
|
||||
|
||||
export interface ReservedAliasesFormProps {}
|
||||
|
||||
export default function ReservedAliasesForm({}: ReservedAliasesFormProps): ReactElement {
|
||||
const {t} = useTranslation()
|
||||
const meUser = useUser()
|
||||
const {showError, showSuccess} = useErrorSuccessSnacks()
|
||||
const navigateToNext = useNavigateToNext("/admin/reserved-aliases")
|
||||
const {data: {users} = {}} = useQuery<GetAdminUsersResponse, AxiosError>(
|
||||
["getAdminUsers"],
|
||||
getAdminUsers,
|
||||
)
|
||||
const {mutateAsync: createAlias} = useMutation<
|
||||
ReservedAlias,
|
||||
AxiosError,
|
||||
CreateReservedAliasData
|
||||
>(createReservedAlias, {
|
||||
onSuccess: () => {
|
||||
showSuccess(t("relations.alias.mutations.success.aliasCreation"))
|
||||
navigateToNext()
|
||||
},
|
||||
})
|
||||
|
||||
console.log(users)
|
||||
const schema = yup.object().shape({
|
||||
local: yup
|
||||
.string()
|
||||
.required()
|
||||
.label(t("routes.AdminRoute.forms.reservedAliases.fields.local.label")),
|
||||
isActive: yup
|
||||
.boolean()
|
||||
.label(t("routes.AdminRoute.forms.reservedAliases.fields.isActive.label")),
|
||||
// Only store IDs of users, as they provide a reference to the user
|
||||
users: yup
|
||||
.array()
|
||||
.of(yup.string())
|
||||
.label(t("routes.AdminRoute.forms.reservedAliases.fields.users.label")),
|
||||
})
|
||||
const formik = useFormik<Form>({
|
||||
validationSchema: schema,
|
||||
initialValues: {
|
||||
local: "",
|
||||
users: [],
|
||||
},
|
||||
onSubmit: async (values, {setErrors, resetForm}) => {
|
||||
try {
|
||||
await createAlias({
|
||||
local: values.local,
|
||||
users: values.users.map(id => ({
|
||||
id,
|
||||
})),
|
||||
})
|
||||
} catch (error) {
|
||||
setErrors(parseFastAPIError(error as AxiosError))
|
||||
}
|
||||
},
|
||||
})
|
||||
const getUser = (id: string) => users?.find(user => user.id === id) as any as ServerUser
|
||||
|
||||
if (!users) return null
|
||||
|
||||
return (
|
||||
<Grid container spacing={4} flexDirection="column" alignItems="center">
|
||||
<Grid item>
|
||||
<form onSubmit={formik.handleSubmit}>
|
||||
<SimpleForm
|
||||
title={t("routes.AdminRoute.forms.reservedAliases.title")}
|
||||
description={t("routes.AdminRoute.forms.reservedAliases.description")}
|
||||
isSubmitting={formik.isSubmitting}
|
||||
continueActionLabel={t(
|
||||
"routes.AdminRoute.forms.reservedAliases.saveAction",
|
||||
)}
|
||||
nonFieldError={formik.errors.nonFieldError}
|
||||
>
|
||||
{[
|
||||
<TextField
|
||||
key="local"
|
||||
fullWidth
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<BiText />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
name="local"
|
||||
id="local"
|
||||
label={t(
|
||||
"routes.AdminRoute.forms.reservedAliases.fields.local.label",
|
||||
)}
|
||||
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}
|
||||
/>,
|
||||
<TextField
|
||||
key="users"
|
||||
fullWidth
|
||||
select
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<HiUsers />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
name="users"
|
||||
id="users"
|
||||
label={t(
|
||||
"routes.AdminRoute.forms.reservedAliases.fields.users.label",
|
||||
)}
|
||||
SelectProps={{
|
||||
multiple: true,
|
||||
value: formik.values.users,
|
||||
onChange: formik.handleChange,
|
||||
}}
|
||||
disabled={formik.isSubmitting}
|
||||
error={formik.touched.users && Boolean(formik.errors.users)}
|
||||
helperText={formik.touched.users && formik.errors.users}
|
||||
>
|
||||
{users.map(user => (
|
||||
<MenuItem key={user.id} value={user.id}>
|
||||
{(() => {
|
||||
// Check if user is me
|
||||
if (user.id === meUser.id) {
|
||||
return t(
|
||||
"routes.AdminRoute.forms.reservedAliases.fields.users.me",
|
||||
{
|
||||
email: user.email.address,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return user.email.address
|
||||
})()}
|
||||
</MenuItem>
|
||||
))}
|
||||
</TextField>,
|
||||
]}
|
||||
</SimpleForm>
|
||||
</form>
|
||||
</Grid>
|
||||
<Grid item>
|
||||
<AliasExplanation
|
||||
local={formik.values.local}
|
||||
emails={formik.values.users.map(userId => getUser(userId).email.address)}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user