diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json
index 4ff93a5..cf01f8f 100644
--- a/public/locales/en-US/translation.json
+++ b/public/locales/en-US/translation.json
@@ -280,6 +280,22 @@
"title": "Log out",
"description": "We are logging you out..."
},
+ "ReservedAliasesRoute": {
+ "title": "Reserved Aliases",
+ "pageActions": {
+ "search": {
+ "label": "Search",
+ "placeholder": "Search for aliases"
+ }
+ },
+ "actions": {
+ "create": {
+ "label": "Create new Reserved Alias"
+ }
+ },
+ "userAmount_one": "Forwards to one user",
+ "userAmount_other": "Forwards to {{count}} users"
+ },
"AdminRoute": {
"title": "Site configuration",
"forms": {
diff --git a/src/App.tsx b/src/App.tsx
index 67e8221..217ee90 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -21,6 +21,7 @@ import LogoutRoute from "~/routes/LogoutRoute"
import OverviewRoute from "~/routes/OverviewRoute"
import ReportDetailRoute from "~/routes/ReportDetailRoute"
import ReportsRoute from "~/routes/ReportsRoute"
+import ReservedAliasesRoute from "~/routes/ReservedAliasesRoute"
import RootRoute from "~/routes/Root"
import SettingsRoute from "~/routes/SettingsRoute"
import SignupRoute from "~/routes/SignupRoute"
@@ -99,6 +100,10 @@ const router = createBrowserRouter([
loader: getServerSettings,
element: ,
},
+ {
+ path: "/admin/reserved-aliases",
+ element: ,
+ },
{
path: "/admin/reserved-aliases/create",
loader: getServerSettings,
diff --git a/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx b/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx
index 70d3528..5462f1f 100644
--- a/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx
+++ b/src/route-widgets/CreateReservedAliasRoute/AliasExplanation.tsx
@@ -4,7 +4,7 @@ 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 {BsArrowRight} from "react-icons/bs"
import {FaMask} from "react-icons/fa"
import {HiUsers} from "react-icons/hi"
@@ -21,13 +21,14 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps)
return (
@@ -35,7 +36,7 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps)
-
+
{t("routes.AdminRoute.forms.reservedAliases.explanation.step1")}
@@ -44,10 +45,10 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps)
-
+
-
+
{t("routes.AdminRoute.forms.reservedAliases.explanation.step2")}
@@ -59,9 +60,11 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps)
-
+
{local}
- @{serverSettings.mailDomain}
+
+ @{serverSettings.mailDomain}
+
@@ -69,10 +72,10 @@ export default function AliasExplanation({local, emails}: AliasExplanationProps)
-
+
-
+
{t("routes.AdminRoute.forms.reservedAliases.explanation.step4")}
diff --git a/src/routes/CreateReservedAliasRoute.tsx b/src/routes/CreateReservedAliasRoute.tsx
index f61df89..7e9f933 100644
--- a/src/routes/CreateReservedAliasRoute.tsx
+++ b/src/routes/CreateReservedAliasRoute.tsx
@@ -77,14 +77,13 @@ export default function CreateReservedAliasRoute(): ReactElement {
})),
})
} catch (error) {
- console.log(parseFastAPIError(error as AxiosError))
setErrors(parseFastAPIError(error as AxiosError))
}
},
})
return (
-
+
-
+ user.email.address)}
+ />
)
diff --git a/src/routes/ReservedAliasesRoute.tsx b/src/routes/ReservedAliasesRoute.tsx
new file mode 100644
index 0000000..493f141
--- /dev/null
+++ b/src/routes/ReservedAliasesRoute.tsx
@@ -0,0 +1,113 @@
+import {ReactElement, useState, useTransition} from "react"
+import {AxiosError} from "axios"
+import {useTranslation} from "react-i18next"
+import {MdAdd, MdSearch} from "react-icons/md"
+import {Link} from "react-router-dom"
+
+import {
+ Button,
+ InputAdornment,
+ List,
+ ListItemButton,
+ ListItemSecondaryAction,
+ ListItemText,
+ Switch,
+ TextField,
+} from "@mui/material"
+import {useQuery} from "@tanstack/react-query"
+
+import {PaginationResult, ReservedAlias} from "~/server-types"
+import {getReservedAliases} from "~/apis"
+import {QueryResult, SimplePage} from "~/components"
+
+export default function ReservedAliasesRoute(): ReactElement {
+ const {t} = useTranslation()
+ const [showSearch, setShowSearch] = useState(false)
+ const [searchValue, setSearchValue] = useState("")
+ const [queryValue, setQueryValue] = useState("")
+ const [, startTransition] = useTransition()
+ const query = useQuery, AxiosError>(
+ ["getReservedAliases", {queryValue}],
+ () =>
+ getReservedAliases({
+ query: queryValue,
+ }),
+ {
+ onSuccess: () => {
+ setShowSearch(true)
+ },
+ },
+ )
+
+ return (
+ {
+ setSearchValue(event.target.value)
+ startTransition(() => {
+ setQueryValue(event.target.value)
+ })
+ }}
+ label={t("routes.ReservedAliasesRoute.pageActions.search.label")}
+ placeholder={t(
+ "routes.ReservedAliasesRoute.pageActions.search.placeholder",
+ )}
+ id="search"
+ InputProps={{
+ startAdornment: (
+
+
+
+ ),
+ }}
+ />
+ )
+ }
+ actions={
+ }
+ to="/admin/reserved-aliases/create"
+ variant="contained"
+ >
+ {t("routes.ReservedAliasesRoute.actions.create.label")}
+
+ }
+ >
+ , AxiosError> query={query}>
+ {({items: aliases}) => (
+
+ {aliases.map(alias => (
+
+
+ {alias.local}
+ @{alias.domain}
+ >
+ }
+ secondary={t("routes.ReservedAliasesRoute.userAmount", {
+ count: alias.users.length,
+ })}
+ />
+
+
+
+
+ ))}
+
+ )}
+
+
+ )
+}
diff --git a/src/server-types.ts b/src/server-types.ts
index 8b6cb7c..2723c40 100644
--- a/src/server-types.ts
+++ b/src/server-types.ts
@@ -103,6 +103,7 @@ export interface Alias {
export interface ReservedAlias {
id: string
+ isActive: boolean
domain: string
local: string
users: Array<{