improved CreateReservedAliasRoute.tsx

This commit is contained in:
Myzel394 2023-02-04 13:11:50 +01:00
parent 32f8d17418
commit dead769e88
4 changed files with 170 additions and 81 deletions

View File

@ -13,7 +13,9 @@ import AliasesRoute from "~/routes/AliasesRoute"
import AuthenticateRoute from "~/routes/AuthenticateRoute"
import AuthenticatedRoute from "~/routes/AuthenticatedRoute"
import CompleteAccountRoute from "~/routes/CompleteAccountRoute"
import CreateReservedAliasRoute from "~/routes/CreateReservedAliasRoute"
import EnterDecryptionPassword from "~/routes/EnterDecryptionPassword"
import I18nHandler from "./I18nHandler"
import LoginRoute from "~/routes/LoginRoute"
import LogoutRoute from "~/routes/LogoutRoute"
import OverviewRoute from "~/routes/OverviewRoute"
@ -23,9 +25,6 @@ import RootRoute from "~/routes/Root"
import SettingsRoute from "~/routes/SettingsRoute"
import SignupRoute from "~/routes/SignupRoute"
import VerifyEmailRoute from "~/routes/VerifyEmailRoute"
import AdminRoute from "~/routes/AdminRoute"
import I18nHandler from "./I18nHandler"
import "./init-i18n"
const router = createBrowserRouter([
@ -101,9 +100,9 @@ const router = createBrowserRouter([
element: <EnterDecryptionPassword />,
},
{
path: "/admin",
path: "/admin/reserved-aliases/create",
loader: getServerSettings,
element: <AdminRoute />,
element: <CreateReservedAliasRoute />,
},
],
},

View 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>
)
}

View File

@ -3,44 +3,32 @@ import {ReactElement} from "react"
import {AxiosError} from "axios"
import {useTranslation} from "react-i18next"
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 {HiUsers} from "react-icons/hi"
import {useErrorSuccessSnacks, useNavigateToNext, useUser} from "~/hooks"
import {ReservedAlias, ServerUser} from "~/server-types"
import {useMutation} from "@tanstack/react-query"
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 {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 {
local: string
users: string[]
users: GetAdminUsersResponse["users"]
isActive?: boolean
nonFieldError?: string
detail?: string
}
export interface ReservedAliasesFormProps {}
export default function ReservedAliasesForm({}: ReservedAliasesFormProps): ReactElement {
export default function CreateReservedAliasRoute(): ReactElement {
const {t} = useTranslation()
const meUser = useUser()
const {showError, showSuccess} = useErrorSuccessSnacks()
const {showSuccess} = useErrorSuccessSnacks()
const navigateToNext = useNavigateToNext("/admin/reserved-aliases")
const {data: {users} = {}} = useQuery<GetAdminUsersResponse, AxiosError>(
["getAdminUsers"],
getAdminUsers,
)
const {mutateAsync: createAlias} = useMutation<
ReservedAlias,
AxiosError,
@ -63,7 +51,15 @@ export default function ReservedAliasesForm({}: ReservedAliasesFormProps): React
// Only store IDs of users, as they provide a reference to the user
users: yup
.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")),
})
const formik = useFormik<Form>({
@ -76,18 +72,16 @@ export default function ReservedAliasesForm({}: ReservedAliasesFormProps): React
try {
await createAlias({
local: values.local,
users: values.users.map(id => ({
id,
users: values.users.map(user => ({
id: user.id,
})),
})
} catch (error) {
console.log(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 (
<Grid container spacing={4} flexDirection="column" alignItems="center">
@ -100,9 +94,11 @@ export default function ReservedAliasesForm({}: ReservedAliasesFormProps): React
continueActionLabel={t(
"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
key="local"
fullWidth
@ -124,58 +120,20 @@ export default function ReservedAliasesForm({}: ReservedAliasesFormProps): React
error={formik.touched.local && Boolean(formik.errors.local)}
helperText={formik.touched.local && formik.errors.local}
/>,
<TextField
<UsersSelectField
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,
}}
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>,
helperText={formik.errors.users as string}
/>,
]}
</SimpleForm>
</form>
</Grid>
<Grid item>
<AliasExplanation
local={formik.values.local}
emails={formik.values.users.map(userId => getUser(userId).email.address)}
/>
<AliasExplanation local={formik.values.local} emails={[]} />
</Grid>
</Grid>
)