mirror of
https://github.com/Myzel394/kleckrelay-website.git
synced 2025-06-20 00:05:26 +02:00
improved CreateReservedAliasRoute.tsx
This commit is contained in:
parent
32f8d17418
commit
dead769e88
@ -13,7 +13,9 @@ import AliasesRoute from "~/routes/AliasesRoute"
|
|||||||
import AuthenticateRoute from "~/routes/AuthenticateRoute"
|
import AuthenticateRoute from "~/routes/AuthenticateRoute"
|
||||||
import AuthenticatedRoute from "~/routes/AuthenticatedRoute"
|
import AuthenticatedRoute from "~/routes/AuthenticatedRoute"
|
||||||
import CompleteAccountRoute from "~/routes/CompleteAccountRoute"
|
import CompleteAccountRoute from "~/routes/CompleteAccountRoute"
|
||||||
|
import CreateReservedAliasRoute from "~/routes/CreateReservedAliasRoute"
|
||||||
import EnterDecryptionPassword from "~/routes/EnterDecryptionPassword"
|
import EnterDecryptionPassword from "~/routes/EnterDecryptionPassword"
|
||||||
|
import I18nHandler from "./I18nHandler"
|
||||||
import LoginRoute from "~/routes/LoginRoute"
|
import LoginRoute from "~/routes/LoginRoute"
|
||||||
import LogoutRoute from "~/routes/LogoutRoute"
|
import LogoutRoute from "~/routes/LogoutRoute"
|
||||||
import OverviewRoute from "~/routes/OverviewRoute"
|
import OverviewRoute from "~/routes/OverviewRoute"
|
||||||
@ -23,9 +25,6 @@ import RootRoute from "~/routes/Root"
|
|||||||
import SettingsRoute from "~/routes/SettingsRoute"
|
import SettingsRoute from "~/routes/SettingsRoute"
|
||||||
import SignupRoute from "~/routes/SignupRoute"
|
import SignupRoute from "~/routes/SignupRoute"
|
||||||
import VerifyEmailRoute from "~/routes/VerifyEmailRoute"
|
import VerifyEmailRoute from "~/routes/VerifyEmailRoute"
|
||||||
|
|
||||||
import AdminRoute from "~/routes/AdminRoute"
|
|
||||||
import I18nHandler from "./I18nHandler"
|
|
||||||
import "./init-i18n"
|
import "./init-i18n"
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
@ -101,9 +100,9 @@ const router = createBrowserRouter([
|
|||||||
element: <EnterDecryptionPassword />,
|
element: <EnterDecryptionPassword />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/admin",
|
path: "/admin/reserved-aliases/create",
|
||||||
loader: getServerSettings,
|
loader: getServerSettings,
|
||||||
element: <AdminRoute />,
|
element: <CreateReservedAliasRoute />,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
132
src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx
Normal file
132
src/route-widgets/CreateReservedAliasRoute/UsersSelectField.tsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import {ReactElement} from "react"
|
||||||
|
import {HiUsers} from "react-icons/hi"
|
||||||
|
import {useTranslation} from "react-i18next"
|
||||||
|
import {AxiosError} from "axios"
|
||||||
|
|
||||||
|
import {useQuery} from "@tanstack/react-query"
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Checkbox,
|
||||||
|
Chip,
|
||||||
|
FormControl,
|
||||||
|
FormHelperText,
|
||||||
|
InputAdornment,
|
||||||
|
InputLabel,
|
||||||
|
ListItemText,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
SelectProps,
|
||||||
|
} from "@mui/material"
|
||||||
|
|
||||||
|
import {GetAdminUsersResponse, getAdminUsers} from "~/apis"
|
||||||
|
import {useUser} from "~/hooks"
|
||||||
|
|
||||||
|
export interface UsersSelectFieldProps extends Omit<SelectProps, "onChange" | "value"> {
|
||||||
|
onChange: SelectProps<GetAdminUsersResponse["users"]>["onChange"]
|
||||||
|
value: GetAdminUsersResponse["users"]
|
||||||
|
|
||||||
|
helperText?: string | string[]
|
||||||
|
error?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UsersSelectField({
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
helperText,
|
||||||
|
error,
|
||||||
|
...props
|
||||||
|
}: UsersSelectFieldProps): ReactElement {
|
||||||
|
const {t} = useTranslation()
|
||||||
|
const meUser = useUser()
|
||||||
|
const {data: {users} = {}} = useQuery<GetAdminUsersResponse, AxiosError>(
|
||||||
|
["getAdminUsers"],
|
||||||
|
getAdminUsers,
|
||||||
|
)
|
||||||
|
const findUser = (id: string) => users?.find(user => user.id === id)
|
||||||
|
const userIds = value?.map(user => user.id) || []
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormControl sx={{minWidth: 180}}>
|
||||||
|
<InputLabel id="users-select" error={error}>
|
||||||
|
{t("routes.AdminRoute.forms.reservedAliases.fields.users.label")}
|
||||||
|
</InputLabel>
|
||||||
|
<Select<string[]>
|
||||||
|
{...props}
|
||||||
|
multiple
|
||||||
|
labelId="users-select"
|
||||||
|
defaultValue={[]}
|
||||||
|
value={userIds}
|
||||||
|
startAdornment={
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<HiUsers />
|
||||||
|
</InputAdornment>
|
||||||
|
}
|
||||||
|
renderValue={(selected: string[]) => (
|
||||||
|
<Box sx={{display: "flex", flexWrap: "wrap", gap: 0.5}}>
|
||||||
|
{selected.map(value => (
|
||||||
|
<Chip key={value} label={findUser(value)!.email.address} />
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
onChange={(event, child) => {
|
||||||
|
if (!Array.isArray(event.target.value)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(event.target.value)
|
||||||
|
// Since there will probably only be a few admin users, n^2 is fine
|
||||||
|
const selectedUsers = (event.target.value as string[]).map(id =>
|
||||||
|
users!.find(user => user.id === id),
|
||||||
|
)
|
||||||
|
console.log(selectedUsers)
|
||||||
|
|
||||||
|
if (!selectedUsers) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
onChange!(
|
||||||
|
// @ts-ignore
|
||||||
|
{
|
||||||
|
...event,
|
||||||
|
target: {
|
||||||
|
...event.target,
|
||||||
|
value: selectedUsers as GetAdminUsersResponse["users"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
child,
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
name="users"
|
||||||
|
id="users"
|
||||||
|
error={error}
|
||||||
|
label={t("routes.AdminRoute.forms.reservedAliases.fields.users.label")}
|
||||||
|
>
|
||||||
|
{users ? (
|
||||||
|
users.map(user => (
|
||||||
|
<MenuItem key={user.id} value={user.id}>
|
||||||
|
<Checkbox checked={userIds.includes(user.id)} />
|
||||||
|
<ListItemText
|
||||||
|
primary={(() => {
|
||||||
|
// 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>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<MenuItem value={""}>{t("general.loading")}</MenuItem>
|
||||||
|
)}
|
||||||
|
</Select>
|
||||||
|
{helperText ? <FormHelperText error={error}>{helperText}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)
|
||||||
|
}
|
@ -3,44 +3,32 @@ import {ReactElement} from "react"
|
|||||||
import {AxiosError} from "axios"
|
import {AxiosError} from "axios"
|
||||||
import {useTranslation} from "react-i18next"
|
import {useTranslation} from "react-i18next"
|
||||||
import {useFormik} from "formik"
|
import {useFormik} from "formik"
|
||||||
|
|
||||||
import {useMutation, useQuery} from "@tanstack/react-query"
|
|
||||||
|
|
||||||
import {
|
|
||||||
CreateReservedAliasData,
|
|
||||||
GetAdminUsersResponse,
|
|
||||||
createReservedAlias,
|
|
||||||
getAdminUsers,
|
|
||||||
} from "~/apis"
|
|
||||||
import {Grid, InputAdornment, MenuItem, TextField} from "@mui/material"
|
|
||||||
import {BiText} from "react-icons/bi"
|
import {BiText} from "react-icons/bi"
|
||||||
import {HiUsers} from "react-icons/hi"
|
|
||||||
import {useErrorSuccessSnacks, useNavigateToNext, useUser} from "~/hooks"
|
import {useMutation} from "@tanstack/react-query"
|
||||||
import {ReservedAlias, ServerUser} from "~/server-types"
|
import {Grid, InputAdornment, TextField} from "@mui/material"
|
||||||
|
|
||||||
|
import {CreateReservedAliasData, GetAdminUsersResponse, createReservedAlias} from "~/apis"
|
||||||
|
import {useErrorSuccessSnacks, useNavigateToNext} from "~/hooks"
|
||||||
|
import {ReservedAlias} from "~/server-types"
|
||||||
import {parseFastAPIError} from "~/utils"
|
import {parseFastAPIError} from "~/utils"
|
||||||
import {SimpleForm} from "~/components"
|
import {SimpleForm} from "~/components"
|
||||||
import AliasExplanation from "~/route-widgets/AdminPage/AliasExplanation"
|
import AliasExplanation from "~/route-widgets/CreateReservedAliasRoute/AliasExplanation"
|
||||||
|
import UsersSelectField from "~/route-widgets/CreateReservedAliasRoute/UsersSelectField"
|
||||||
|
|
||||||
interface Form {
|
interface Form {
|
||||||
local: string
|
local: string
|
||||||
users: string[]
|
users: GetAdminUsersResponse["users"]
|
||||||
|
|
||||||
isActive?: boolean
|
isActive?: boolean
|
||||||
|
|
||||||
nonFieldError?: string
|
detail?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ReservedAliasesFormProps {}
|
export default function CreateReservedAliasRoute(): ReactElement {
|
||||||
|
|
||||||
export default function ReservedAliasesForm({}: ReservedAliasesFormProps): ReactElement {
|
|
||||||
const {t} = useTranslation()
|
const {t} = useTranslation()
|
||||||
const meUser = useUser()
|
const {showSuccess} = useErrorSuccessSnacks()
|
||||||
const {showError, showSuccess} = useErrorSuccessSnacks()
|
|
||||||
const navigateToNext = useNavigateToNext("/admin/reserved-aliases")
|
const navigateToNext = useNavigateToNext("/admin/reserved-aliases")
|
||||||
const {data: {users} = {}} = useQuery<GetAdminUsersResponse, AxiosError>(
|
|
||||||
["getAdminUsers"],
|
|
||||||
getAdminUsers,
|
|
||||||
)
|
|
||||||
const {mutateAsync: createAlias} = useMutation<
|
const {mutateAsync: createAlias} = useMutation<
|
||||||
ReservedAlias,
|
ReservedAlias,
|
||||||
AxiosError,
|
AxiosError,
|
||||||
@ -63,7 +51,15 @@ export default function ReservedAliasesForm({}: ReservedAliasesFormProps): React
|
|||||||
// Only store IDs of users, as they provide a reference to the user
|
// Only store IDs of users, as they provide a reference to the user
|
||||||
users: yup
|
users: yup
|
||||||
.array()
|
.array()
|
||||||
.of(yup.string())
|
.of(
|
||||||
|
yup.object().shape({
|
||||||
|
id: yup.string(),
|
||||||
|
email: yup.object().shape({
|
||||||
|
id: yup.string(),
|
||||||
|
address: yup.string(),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
.label(t("routes.AdminRoute.forms.reservedAliases.fields.users.label")),
|
.label(t("routes.AdminRoute.forms.reservedAliases.fields.users.label")),
|
||||||
})
|
})
|
||||||
const formik = useFormik<Form>({
|
const formik = useFormik<Form>({
|
||||||
@ -76,18 +72,16 @@ export default function ReservedAliasesForm({}: ReservedAliasesFormProps): React
|
|||||||
try {
|
try {
|
||||||
await createAlias({
|
await createAlias({
|
||||||
local: values.local,
|
local: values.local,
|
||||||
users: values.users.map(id => ({
|
users: values.users.map(user => ({
|
||||||
id,
|
id: user.id,
|
||||||
})),
|
})),
|
||||||
})
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(parseFastAPIError(error as AxiosError))
|
||||||
setErrors(parseFastAPIError(error as AxiosError))
|
setErrors(parseFastAPIError(error as AxiosError))
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
const getUser = (id: string) => users?.find(user => user.id === id) as any as ServerUser
|
|
||||||
|
|
||||||
if (!users) return null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={4} flexDirection="column" alignItems="center">
|
<Grid container spacing={4} flexDirection="column" alignItems="center">
|
||||||
@ -100,9 +94,11 @@ export default function ReservedAliasesForm({}: ReservedAliasesFormProps): React
|
|||||||
continueActionLabel={t(
|
continueActionLabel={t(
|
||||||
"routes.AdminRoute.forms.reservedAliases.saveAction",
|
"routes.AdminRoute.forms.reservedAliases.saveAction",
|
||||||
)}
|
)}
|
||||||
nonFieldError={formik.errors.nonFieldError}
|
nonFieldError={formik.errors.detail}
|
||||||
>
|
>
|
||||||
{[
|
{[
|
||||||
|
// We can improve this by using a custom component
|
||||||
|
// that directly shows whether the alias is available or not
|
||||||
<TextField
|
<TextField
|
||||||
key="local"
|
key="local"
|
||||||
fullWidth
|
fullWidth
|
||||||
@ -124,58 +120,20 @@ export default function ReservedAliasesForm({}: ReservedAliasesFormProps): React
|
|||||||
error={formik.touched.local && Boolean(formik.errors.local)}
|
error={formik.touched.local && Boolean(formik.errors.local)}
|
||||||
helperText={formik.touched.local && formik.errors.local}
|
helperText={formik.touched.local && formik.errors.local}
|
||||||
/>,
|
/>,
|
||||||
<TextField
|
<UsersSelectField
|
||||||
key="users"
|
key="users"
|
||||||
fullWidth
|
value={formik.values.users}
|
||||||
select
|
onChange={formik.handleChange}
|
||||||
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}
|
disabled={formik.isSubmitting}
|
||||||
error={formik.touched.users && Boolean(formik.errors.users)}
|
error={formik.touched.users && Boolean(formik.errors.users)}
|
||||||
helperText={formik.touched.users && formik.errors.users}
|
helperText={formik.errors.users as string}
|
||||||
>
|
/>,
|
||||||
{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>
|
</SimpleForm>
|
||||||
</form>
|
</form>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<AliasExplanation
|
<AliasExplanation local={formik.values.local} emails={[]} />
|
||||||
local={formik.values.local}
|
|
||||||
emails={formik.values.users.map(userId => getUser(userId).email.address)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
)
|
)
|
Loading…
x
Reference in New Issue
Block a user