From 517b49a5c43269b2215d4c0454dbc46dcdfb38f8 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Wed, 1 Feb 2023 19:55:10 +0100
Subject: [PATCH 01/30] current stand
---
src/App.tsx | 4 ++++
.../AuthenticateRoute/NavigationButton.tsx | 6 +++++-
src/routes/AdminRoute.tsx | 15 +++++++++++++++
src/server-types.ts | 1 +
4 files changed, 25 insertions(+), 1 deletion(-)
create mode 100644 src/routes/AdminRoute.tsx
diff --git a/src/App.tsx b/src/App.tsx
index f2578fd..8f2111d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -99,6 +99,10 @@ const router = createBrowserRouter([
loader: getServerSettings,
element: ,
},
+ {
+ path: "/admin",
+ element: ,
+ },
],
},
],
diff --git a/src/route-widgets/AuthenticateRoute/NavigationButton.tsx b/src/route-widgets/AuthenticateRoute/NavigationButton.tsx
index 0ef1b8e..022fc05 100644
--- a/src/route-widgets/AuthenticateRoute/NavigationButton.tsx
+++ b/src/route-widgets/AuthenticateRoute/NavigationButton.tsx
@@ -1,7 +1,7 @@
import {ReactElement, useContext} from "react"
import {BiStats} from "react-icons/bi"
import {MdSettings} from "react-icons/md"
-import {FaMask} from "react-icons/fa"
+import {FaMask, FaServer} from "react-icons/fa"
import {Link as RouterLink, useLocation} from "react-router-dom"
import {useTranslation} from "react-i18next"
@@ -16,6 +16,7 @@ export enum NavigationSection {
Aliases,
Reports,
Settings,
+ Admin,
}
export interface NavigationButtonProps {
@@ -27,6 +28,7 @@ const SECTION_ICON_MAP: Record = {
[NavigationSection.Aliases]: ,
[NavigationSection.Reports]: ,
[NavigationSection.Settings]: ,
+ [NavigationSection.Admin]: ,
}
const SECTION_TEXT_MAP: Record = {
@@ -34,6 +36,7 @@ const SECTION_TEXT_MAP: Record = {
[NavigationSection.Aliases]: "components.NavigationButton.aliases",
[NavigationSection.Reports]: "components.NavigationButton.reports",
[NavigationSection.Settings]: "components.NavigationButton.settings",
+ [NavigationSection.Admin]: "components.NavigationButton.admin",
}
const PATH_SECTION_MAP: Record = {
@@ -41,6 +44,7 @@ const PATH_SECTION_MAP: Record = {
aliases: NavigationSection.Aliases,
reports: NavigationSection.Reports,
settings: NavigationSection.Settings,
+ admin: NavigationSection.Admin,
}
export default function NavigationButton({section}: NavigationButtonProps): ReactElement {
diff --git a/src/routes/AdminRoute.tsx b/src/routes/AdminRoute.tsx
new file mode 100644
index 0000000..e2b9f66
--- /dev/null
+++ b/src/routes/AdminRoute.tsx
@@ -0,0 +1,15 @@
+import {ReactElement, useLayoutEffect} from "react";
+import {useNavigateToNext, useUser} from "~/hooks";
+
+export default function AdminRoute(): ReactElement {
+ const navigateToNext = useNavigateToNext();
+ const user = useUser();
+
+ useLayoutEffect(() => {
+ if (!user.isAdmin) {
+ navigateToNext();
+ }
+ }, [user.isAdmin, navigateToNext])
+
+
+}
diff --git a/src/server-types.ts b/src/server-types.ts
index 9669ec1..eeef306 100644
--- a/src/server-types.ts
+++ b/src/server-types.ts
@@ -41,6 +41,7 @@ export interface ServerUser {
isDecrypted: false
encryptedPassword: string
salt: string
+ isAdmin: boolean
email: {
address: string
isVerified: boolean
From fde7705850c04febbd4724e88be38ab15707141b Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Thu, 2 Feb 2023 21:12:14 +0100
Subject: [PATCH 02/30] added ReservedAliasesForm.tsx
---
public/locales/en-US/translation.json | 6 +++-
src/App.tsx | 3 +-
src/apis/get-admin-users.ts | 19 ++++++++++++
src/apis/get-reserved-aliases.ts | 23 ++++++++++++++
src/apis/index.ts | 4 +++
.../AdminPage/ReservedAliasesForm.tsx | 17 ++++++++++
src/routes/AdminRoute.tsx | 31 +++++++++++++------
src/server-types.ts | 10 ++++++
8 files changed, 101 insertions(+), 12 deletions(-)
create mode 100644 src/apis/get-admin-users.ts
create mode 100644 src/apis/get-reserved-aliases.ts
create mode 100644 src/route-widgets/AdminPage/ReservedAliasesForm.tsx
diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json
index 8cffdb8..8f0ede7 100644
--- a/public/locales/en-US/translation.json
+++ b/public/locales/en-US/translation.json
@@ -279,6 +279,9 @@
"LogoutRoute": {
"title": "Log out",
"description": "We are logging you out..."
+ },
+ "AdminRoute": {
+ "title": "Site configuration"
}
},
@@ -287,7 +290,8 @@
"overview": "Overview",
"aliases": "Aliases",
"reports": "Reports",
- "settings": "Settings"
+ "settings": "Settings",
+ "admin": "Admin"
},
"AuthenticateRoute": {
"signup": "Sign up",
diff --git a/src/App.tsx b/src/App.tsx
index 8f2111d..8771a11 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -24,6 +24,7 @@ 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"
@@ -101,7 +102,7 @@ const router = createBrowserRouter([
},
{
path: "/admin",
- element: ,
+ element: ,
},
],
},
diff --git a/src/apis/get-admin-users.ts b/src/apis/get-admin-users.ts
new file mode 100644
index 0000000..8d34dc6
--- /dev/null
+++ b/src/apis/get-admin-users.ts
@@ -0,0 +1,19 @@
+import {client} from "~/constants/axios-client"
+
+export interface GetAdminUsersResponse {
+ users: Array<{
+ id: string
+ email: {
+ id: string
+ address: string
+ }
+ }>
+}
+
+export default async function getAdminUsers(): Promise {
+ const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/admin/users`, {
+ withCredentials: true,
+ })
+
+ return data
+}
diff --git a/src/apis/get-reserved-aliases.ts b/src/apis/get-reserved-aliases.ts
new file mode 100644
index 0000000..8cfb5ce
--- /dev/null
+++ b/src/apis/get-reserved-aliases.ts
@@ -0,0 +1,23 @@
+import {GetPageData, PaginationResult, ReservedAlias} from "~/server-types"
+import {client} from "~/constants/axios-client"
+
+export interface GetReservedAliasesData extends GetPageData {
+ query?: string
+}
+
+export default async function getReservedAliases({
+ query,
+ size,
+ page,
+}: GetReservedAliasesData = {}): Promise> {
+ const {data} = await client.get(`${import.meta.env.VITE_SERVER_BASE_URL}/v1/reserved-alias/`, {
+ withCredentials: true,
+ params: {
+ query,
+ size,
+ page,
+ },
+ })
+
+ return data
+}
diff --git a/src/apis/index.ts b/src/apis/index.ts
index 81a7522..ddf3005 100644
--- a/src/apis/index.ts
+++ b/src/apis/index.ts
@@ -38,3 +38,7 @@ export * from "./delete-report"
export {default as deleteReport} from "./delete-report"
export * from "./get-me"
export {default as getMe} from "./get-me"
+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"
diff --git a/src/route-widgets/AdminPage/ReservedAliasesForm.tsx b/src/route-widgets/AdminPage/ReservedAliasesForm.tsx
new file mode 100644
index 0000000..86fac79
--- /dev/null
+++ b/src/route-widgets/AdminPage/ReservedAliasesForm.tsx
@@ -0,0 +1,17 @@
+import {ReactElement} from "react"
+import {AxiosError} from "axios"
+
+import {useQuery} from "@tanstack/react-query"
+
+import {GetAdminUsersResponse, getAdminUsers} from "~/apis"
+
+export interface ReservedAliasesFormProps {}
+
+export default function ReservedAliasesForm({}: ReservedAliasesFormProps): ReactElement {
+ const {data: {users} = {}} = useQuery(
+ ["getAdminUsers"],
+ getAdminUsers,
+ )
+
+ console.log(users)
+}
diff --git a/src/routes/AdminRoute.tsx b/src/routes/AdminRoute.tsx
index e2b9f66..c34fda8 100644
--- a/src/routes/AdminRoute.tsx
+++ b/src/routes/AdminRoute.tsx
@@ -1,15 +1,26 @@
-import {ReactElement, useLayoutEffect} from "react";
-import {useNavigateToNext, useUser} from "~/hooks";
+import {ReactElement, useLayoutEffect} from "react"
+import {useTranslation} from "react-i18next"
+
+import {SimplePageBuilder} from "~/components"
+import {useNavigateToNext, useUser} from "~/hooks"
+import ReservedAliasesForm from "~/route-widgets/AdminPage/ReservedAliasesForm"
+import ReservedAliasesList from "~/route-widgets/AdminPage/ReservedAliasesList"
export default function AdminRoute(): ReactElement {
- const navigateToNext = useNavigateToNext();
- const user = useUser();
-
- useLayoutEffect(() => {
- if (!user.isAdmin) {
- navigateToNext();
- }
- }, [user.isAdmin, navigateToNext])
+ const {t} = useTranslation()
+ const navigateToNext = useNavigateToNext()
+ const user = useUser()
+ useLayoutEffect(() => {
+ if (!user.isAdmin) {
+ navigateToNext()
+ }
+ }, [user.isAdmin, navigateToNext])
+ return (
+
+
+
+
+ )
}
diff --git a/src/server-types.ts b/src/server-types.ts
index eeef306..8b6cb7c 100644
--- a/src/server-types.ts
+++ b/src/server-types.ts
@@ -101,6 +101,16 @@ export interface Alias {
prefExpandUrlShorteners: boolean | null
}
+export interface ReservedAlias {
+ id: string
+ domain: string
+ local: string
+ users: Array<{
+ id: string
+ email: string
+ }>
+}
+
export interface AliasNote {
version: "1.0"
data: {
From cd1fe2005af4d7fcae63ec5fe2455653779777ce Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Thu, 2 Feb 2023 21:23:50 +0100
Subject: [PATCH 03/30] added ReservedAliasesList.tsx
---
.../AdminPage/ReservedAliasesList.tsx | 32 +++++++++++++++++++
1 file changed, 32 insertions(+)
create mode 100644 src/route-widgets/AdminPage/ReservedAliasesList.tsx
diff --git a/src/route-widgets/AdminPage/ReservedAliasesList.tsx b/src/route-widgets/AdminPage/ReservedAliasesList.tsx
new file mode 100644
index 0000000..b856845
--- /dev/null
+++ b/src/route-widgets/AdminPage/ReservedAliasesList.tsx
@@ -0,0 +1,32 @@
+import {ReactElement} from "react"
+import {AxiosError} from "axios"
+
+import {useQuery} from "@tanstack/react-query"
+import {List, ListItem, ListItemText} from "@mui/material"
+
+import {getReservedAliases} from "~/apis"
+import {PaginationResult, ReservedAlias} from "~/server-types"
+import {QueryResult} from "~/components"
+
+export interface ReservedAliasesListProps {}
+
+export default function ReservedAliasesList({}: ReservedAliasesListProps): ReactElement {
+ const query = useQuery, AxiosError>(
+ ["getReservedAliases"],
+ () => getReservedAliases(),
+ )
+
+ return (
+ , AxiosError> query={query}>
+ {({items}) => (
+
+ {items.map(alias => (
+
+
+
+ ))}
+
+ )}
+
+ )
+}
From 32f8d17418a0b454145258e9cba310726385bdf3 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sat, 4 Feb 2023 12:12:33 +0100
Subject: [PATCH 04/30] added Reserved Alias creation
---
public/locales/en-US/translation.json | 23 ++-
src/App.tsx | 1 +
src/apis/create-reserved-alias.ts | 25 +++
src/apis/index.ts | 2 +
src/components/widgets/SimpleForm.tsx | 2 +-
.../AdminPage/AliasExplanation.tsx | 99 ++++++++++
.../AdminPage/ReservedAliasesForm.tsx | 171 +++++++++++++++++-
7 files changed, 318 insertions(+), 5 deletions(-)
create mode 100644 src/apis/create-reserved-alias.ts
create mode 100644 src/route-widgets/AdminPage/AliasExplanation.tsx
diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json
index 8f0ede7..4ff93a5 100644
--- a/public/locales/en-US/translation.json
+++ b/public/locales/en-US/translation.json
@@ -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"
+ }
+ }
+ }
}
},
diff --git a/src/App.tsx b/src/App.tsx
index 8771a11..62d9b06 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -102,6 +102,7 @@ const router = createBrowserRouter([
},
{
path: "/admin",
+ loader: getServerSettings,
element: ,
},
],
diff --git a/src/apis/create-reserved-alias.ts b/src/apis/create-reserved-alias.ts
new file mode 100644
index 0000000..28c1b96
--- /dev/null
+++ b/src/apis/create-reserved-alias.ts
@@ -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 {
+ const {data} = await client.post(
+ `${import.meta.env.VITE_SERVER_BASE_URL}/v1/reserved-alias`,
+ aliasData,
+ {
+ withCredentials: true,
+ },
+ )
+
+ return data
+}
diff --git a/src/apis/index.ts b/src/apis/index.ts
index ddf3005..438a419 100644
--- a/src/apis/index.ts
+++ b/src/apis/index.ts
@@ -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"
diff --git a/src/components/widgets/SimpleForm.tsx b/src/components/widgets/SimpleForm.tsx
index d8524d4..36fc510 100644
--- a/src/components/widgets/SimpleForm.tsx
+++ b/src/components/widgets/SimpleForm.tsx
@@ -73,7 +73,7 @@ export default function SimpleForm({
{children.map(input => (
-
+
{input}
))}
diff --git a/src/route-widgets/AdminPage/AliasExplanation.tsx b/src/route-widgets/AdminPage/AliasExplanation.tsx
new file mode 100644
index 0000000..70d3528
--- /dev/null
+++ b/src/route-widgets/AdminPage/AliasExplanation.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+ {t("routes.AdminRoute.forms.reservedAliases.explanation.step1")}
+
+
+
+
+
+
+
+
+
+
+
+ {t("routes.AdminRoute.forms.reservedAliases.explanation.step2")}
+
+
+
+
+
+
+
+
+
+
+
+ {local}
+ @{serverSettings.mailDomain}
+
+
+
+
+
+
+
+
+
+
+
+ {t("routes.AdminRoute.forms.reservedAliases.explanation.step4")}
+
+
+
+
+
+
+
+
+
+
+
+ {emails.map(email => (
+
+
+
+ ))}
+
+
+
+
+
+ )
+}
diff --git a/src/route-widgets/AdminPage/ReservedAliasesForm.tsx b/src/route-widgets/AdminPage/ReservedAliasesForm.tsx
index 86fac79..e8309ed 100644
--- a/src/route-widgets/AdminPage/ReservedAliasesForm.tsx
+++ b/src/route-widgets/AdminPage/ReservedAliasesForm.tsx
@@ -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(
["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
- getUser(userId).email.address)}
- />
+
)
From dac14af539589da003926c63f15dce675c009db6 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sat, 4 Feb 2023 14:36:26 +0100
Subject: [PATCH 06/30] created ReservedAliasesRoute.tsx
---
public/locales/en-US/translation.json | 16 +++
src/App.tsx | 5 +
.../AliasExplanation.tsx | 21 ++--
src/routes/CreateReservedAliasRoute.tsx | 8 +-
src/routes/ReservedAliasesRoute.tsx | 113 ++++++++++++++++++
src/server-types.ts | 1 +
6 files changed, 152 insertions(+), 12 deletions(-)
create mode 100644 src/routes/ReservedAliasesRoute.tsx
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<{
From 90dd7ca180cae307fc771df2796a5b402ff19fbe Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sat, 4 Feb 2023 16:43:38 +0100
Subject: [PATCH 07/30] added ReservedAliasDetailRoute.tsx
---
public/locales/en-US/translation.json | 14 ++
src/App.tsx | 5 +
src/apis/get-reserved-alias.ts | 13 ++
src/apis/index.ts | 4 +
src/apis/update-reserved-alias.ts | 27 +++
.../AdminUserPicker.tsx | 65 ++++++
.../AliasActivationSwitch.tsx | 86 ++++++++
.../AliasUsersList.tsx | 205 ++++++++++++++++++
src/routes/ReservedAliasDetailRoute.tsx | 59 +++++
src/server-types.ts | 5 +-
src/utils/parse-fastapi-error.ts | 4 +
11 files changed, 486 insertions(+), 1 deletion(-)
create mode 100644 src/apis/get-reserved-alias.ts
create mode 100644 src/apis/update-reserved-alias.ts
create mode 100644 src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx
create mode 100644 src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx
create mode 100644 src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx
create mode 100644 src/routes/ReservedAliasDetailRoute.tsx
diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json
index cf01f8f..c414c7e 100644
--- a/public/locales/en-US/translation.json
+++ b/public/locales/en-US/translation.json
@@ -296,6 +296,20 @@
"userAmount_one": "Forwards to one user",
"userAmount_other": "Forwards to {{count}} users"
},
+ "ReservedAliasDetailRoute": {
+ "title": "Reserved Alias Details",
+ "sections": {
+ "users": {
+ "title": "Users",
+ "fields": {
+ "users": {
+ "label": "Users",
+ "me": "{{email}} (Me)"
+ }
+ }
+ }
+ }
+ },
"AdminRoute": {
"title": "Site configuration",
"forms": {
diff --git a/src/App.tsx b/src/App.tsx
index 217ee90..f2cfed2 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 ReservedAliasDetailRoute from "~/routes/ReservedAliasDetailRoute"
import ReservedAliasesRoute from "~/routes/ReservedAliasesRoute"
import RootRoute from "~/routes/Root"
import SettingsRoute from "~/routes/SettingsRoute"
@@ -104,6 +105,10 @@ const router = createBrowserRouter([
path: "/admin/reserved-aliases",
element: ,
},
+ {
+ path: "/admin/reserved-aliases/:id",
+ element: ,
+ },
{
path: "/admin/reserved-aliases/create",
loader: getServerSettings,
diff --git a/src/apis/get-reserved-alias.ts b/src/apis/get-reserved-alias.ts
new file mode 100644
index 0000000..89a547b
--- /dev/null
+++ b/src/apis/get-reserved-alias.ts
@@ -0,0 +1,13 @@
+import {ReservedAlias} from "~/server-types"
+import {client} from "~/constants/axios-client"
+
+export default async function getReservedAlias(id: string): Promise {
+ const {data} = await client.get(
+ `${import.meta.env.VITE_SERVER_BASE_URL}/v1/reserved-alias/${id}`,
+ {
+ withCredentials: true,
+ },
+ )
+
+ return data
+}
diff --git a/src/apis/index.ts b/src/apis/index.ts
index 438a419..e2ae2ad 100644
--- a/src/apis/index.ts
+++ b/src/apis/index.ts
@@ -44,3 +44,7 @@ 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"
+export * from "./get-reserved-alias"
+export {default as getReservedAlias} from "./get-reserved-alias"
+export * from "./update-reserved-alias"
+export {default as updateReservedAlias} from "./update-reserved-alias"
diff --git a/src/apis/update-reserved-alias.ts b/src/apis/update-reserved-alias.ts
new file mode 100644
index 0000000..f7d217e
--- /dev/null
+++ b/src/apis/update-reserved-alias.ts
@@ -0,0 +1,27 @@
+import {ReservedAlias} from "~/server-types"
+import {client} from "~/constants/axios-client"
+
+export interface UpdateReservedAliasData {
+ isActive?: boolean
+ users?: Array<{
+ id: string
+ }>
+}
+
+export default async function updateReservedAlias(
+ id: string,
+ {isActive, users}: UpdateReservedAliasData,
+): Promise {
+ const {data} = await client.patch(
+ `${import.meta.env.VITE_SERVER_BASE_URL}/v1/reserved-alias/${id}`,
+ {
+ isActive,
+ users,
+ },
+ {
+ withCredentials: true,
+ },
+ )
+
+ return data
+}
diff --git a/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx b/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx
new file mode 100644
index 0000000..a33e68f
--- /dev/null
+++ b/src/route-widgets/ReservedAliasDetailRoute/AdminUserPicker.tsx
@@ -0,0 +1,65 @@
+import {AxiosError} from "axios"
+import {useTranslation} from "react-i18next"
+import {ReactElement} from "react"
+
+import {useQuery} from "@tanstack/react-query"
+import {MenuItem, TextField} from "@mui/material"
+
+import {GetAdminUsersResponse, getAdminUsers} from "~/apis"
+import {useUser} from "~/hooks"
+
+export interface AdminUserPickerProps {
+ onPick: (user: GetAdminUsersResponse["users"][0]) => void
+ alreadyPicked: GetAdminUsersResponse["users"]
+}
+
+export default function AdminUserPicker({
+ onPick,
+ alreadyPicked,
+}: AdminUserPickerProps): ReactElement {
+ const {t} = useTranslation()
+ const meUser = useUser()
+ const {data: {users: availableUsers} = {}} = useQuery(
+ ["getAdminUsers"],
+ getAdminUsers,
+ )
+
+ if (!availableUsers) {
+ return <>>
+ }
+
+ const users = availableUsers.filter(
+ user => !alreadyPicked.find(picked => picked.id === user.id),
+ )
+
+ if (users.length === 0) {
+ return <>>
+ }
+
+ return (
+ {
+ const user = users.find(user => user.id === event.target.value)
+ if (user) {
+ onPick(user)
+ }
+
+ event.preventDefault()
+ }}
+ >
+ {users.map(user => (
+
+ ))}
+
+ )
+}
diff --git a/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx b/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx
new file mode 100644
index 0000000..6057d11
--- /dev/null
+++ b/src/route-widgets/ReservedAliasDetailRoute/AliasActivationSwitch.tsx
@@ -0,0 +1,86 @@
+import {ReactElement} from "react"
+import {AxiosError} from "axios"
+import {useTranslation} from "react-i18next"
+import update from "immutability-helper"
+
+import {useMutation} from "@tanstack/react-query"
+import {Switch} from "@mui/material"
+
+import {useErrorSuccessSnacks} from "~/hooks"
+import {ReservedAlias} from "~/server-types"
+import {updateReservedAlias} from "~/apis"
+import {queryClient} from "~/constants/react-query"
+
+export interface AliasActivationSwitch {
+ id: string
+ isActive: boolean
+ queryKey: readonly string[]
+}
+
+export default function AliasActivationSwitch({
+ id,
+ isActive,
+ queryKey,
+}: AliasActivationSwitch): ReactElement {
+ const {t} = useTranslation()
+ const {showError, showSuccess} = useErrorSuccessSnacks()
+ const {isLoading, mutateAsync} = useMutation<
+ ReservedAlias,
+ AxiosError,
+ boolean,
+ {previousAlias: ReservedAlias | undefined}
+ >(
+ activeNow =>
+ updateReservedAlias(id, {
+ isActive: activeNow,
+ }),
+ {
+ onMutate: async activeNow => {
+ await queryClient.cancelQueries(queryKey)
+
+ const previousAlias = queryClient.getQueryData(queryKey)
+
+ queryClient.setQueryData(queryKey, old =>
+ update(old, {
+ isActive: {
+ $set: activeNow!,
+ },
+ }),
+ )
+
+ return {previousAlias}
+ },
+ onSuccess: newAlias => {
+ queryClient.setQueryData(queryKey, newAlias)
+ },
+ onError: (error, values, context) => {
+ showError(error)
+
+ if (context?.previousAlias) {
+ queryClient.setQueryData(queryKey, context.previousAlias)
+ }
+ },
+ },
+ )
+
+ return (
+ {
+ if (isLoading) {
+ return
+ }
+
+ try {
+ await mutateAsync(!isActive)
+
+ showSuccess(
+ isActive
+ ? t("relations.alias.mutations.success.aliasChangedToDisabled")
+ : t("relations.alias.mutations.success.aliasChangedToEnabled"),
+ )
+ } catch {}
+ }}
+ />
+ )
+}
diff --git a/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx b/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx
new file mode 100644
index 0000000..a5ca752
--- /dev/null
+++ b/src/route-widgets/ReservedAliasDetailRoute/AliasUsersList.tsx
@@ -0,0 +1,205 @@
+import * as yup from "yup"
+import {ReactElement, useState} from "react"
+import {AxiosError} from "axios"
+import {MdCheckCircle} from "react-icons/md"
+import {FaPen} from "react-icons/fa"
+import {useTranslation} from "react-i18next"
+import {FieldArray, FormikProvider, useFormik} from "formik"
+import {TiDelete} from "react-icons/ti"
+import deepEqual from "deep-equal"
+import update from "immutability-helper"
+
+import {useMutation} from "@tanstack/react-query"
+import {
+ Divider,
+ FormHelperText,
+ Grid,
+ IconButton,
+ List,
+ ListItem,
+ ListItemSecondaryAction,
+ ListItemText,
+ Typography,
+} from "@mui/material"
+
+import {ReservedAlias} from "~/server-types"
+import {updateReservedAlias} from "~/apis"
+import {parseFastAPIError} from "~/utils"
+import {queryClient} from "~/constants/react-query"
+import {useErrorSuccessSnacks} from "~/hooks"
+import AdminUserPicker from "~/route-widgets/ReservedAliasDetailRoute/AdminUserPicker"
+
+export interface AliasUsersListProps {
+ users: ReservedAlias["users"]
+ id: string
+ queryKey: readonly string[]
+}
+
+interface Form {
+ users: ReservedAlias["users"]
+}
+
+export default function AliasUsersList({users, queryKey, id}: AliasUsersListProps): ReactElement {
+ const {t} = useTranslation()
+ const {showError, showSuccess} = useErrorSuccessSnacks()
+ const {mutateAsync} = useMutation<
+ ReservedAlias,
+ AxiosError,
+ ReservedAlias["users"],
+ {previousAlias?: ReservedAlias}
+ >(
+ users =>
+ updateReservedAlias(id, {
+ users: users.map(user => ({
+ id: user.id,
+ })),
+ }),
+ {
+ onMutate: async users => {
+ await queryClient.cancelQueries(queryKey)
+
+ const previousAlias = queryClient.getQueryData(queryKey)
+
+ queryClient.setQueryData(queryKey, old =>
+ update(old, {
+ users: {
+ $set: users,
+ },
+ }),
+ )
+
+ return {
+ previousAlias,
+ }
+ },
+ onSuccess: async newAlias => {
+ showSuccess(t("relations.alias.mutations.success.aliasUpdated"))
+
+ await queryClient.cancelQueries(queryKey)
+
+ queryClient.setQueryData(queryKey, newAlias as any as ReservedAlias)
+ },
+ onError: (error, _, context) => {
+ showError(error)
+
+ setIsInEditMode(true)
+
+ if (context?.previousAlias) {
+ queryClient.setQueryData(queryKey, context.previousAlias)
+ }
+ },
+ },
+ )
+ const schema = yup.object().shape({
+ users: yup
+ .array()
+ .of(
+ yup.object().shape({
+ id: yup.string().required(),
+ email: yup.object().shape({
+ address: yup.string().required(),
+ id: yup.string().required(),
+ }),
+ }),
+ )
+ .label(t("routes.AliasDetailRoute.sections.users.fields.users.label")),
+ })
+ const initialValues: Form = {
+ users: users,
+ }
+ const formik = useFormik
+
+
- ,
-
-
+
+
+ }
+ />
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+
+ }
+ />
+
+
+
+
+
+ ),
+ }}
+ />
+
+
+
+
+ }
+ label={t(
+ "routes.AdminRoute.forms.settings.userEmailEnableDisposableEmails.label",
+ )}
/>
- }
- label={t("routes.AdminRoute.forms.settings.allowStatistics.label")}
- />
-
- {(formik.touched.allowStatistics && formik.errors.allowStatistics) ||
- t("routes.AdminRoute.forms.settings.allowStatistics.description")}
-
- ,
- ]}
-
+
+ {(formik.touched.userEmailEnableDisposableEmails &&
+ formik.errors.userEmailEnableDisposableEmails) ||
+ t(
+ "routes.AdminRoute.forms.settings.userEmailEnableDisposableEmails.description",
+ )}
+
+
+
+
+
+
+ }
+ label={t(
+ "routes.AdminRoute.forms.settings.userEmailEnableOtherRelays.label",
+ )}
+ />
+
+ {(formik.touched.userEmailEnableOtherRelays &&
+ formik.errors.userEmailEnableOtherRelays) ||
+ t(
+ "routes.AdminRoute.forms.settings.userEmailEnableOtherRelays.description",
+ )}
+
+
+
+
+
+
+ }
+ label={t(
+ "routes.AdminRoute.forms.settings.enableImageProxy.label",
+ )}
+ />
+
+ {(formik.touched.enableImageProxy &&
+ formik.errors.enableImageProxy) ||
+ t(
+ "routes.AdminRoute.forms.settings.enableImageProxy.description",
+ )}
+
+
+
+
+
+
+ }
+ label={t(
+ "routes.AdminRoute.forms.settings.allowStatistics.label",
+ )}
+ />
+
+ {(formik.touched.allowStatistics &&
+ formik.errors.allowStatistics) ||
+ t(
+ "routes.AdminRoute.forms.settings.allowStatistics.description",
+ )}
+
+
+
+
+
+
+
+
+ }
+ >
+ {t("general.saveLabel")}
+
+
+
+
+
)
}
From db072002fe7f2cc3e0566822aa1e044396811a82 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Fri, 10 Feb 2023 20:51:22 +0100
Subject: [PATCH 17/30] feat: Add SettingsForm mutation handling
---
public/locales/en-US/translation.json | 3 +-
.../GlobalSettingsRoute/SettingsForm.tsx | 46 +++++++++++++++++--
2 files changed, 45 insertions(+), 4 deletions(-)
diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json
index d1419ef..e069221 100644
--- a/public/locales/en-US/translation.json
+++ b/public/locales/en-US/translation.json
@@ -382,7 +382,8 @@
},
"settings": {
"title": "Global Settings",
- "description": "Configure global settings for your instance."
+ "description": "Configure global settings for your instance.",
+ "successMessage": "Settings have been saved successfully!"
}
}
},
diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
index db9faa9..f32288a 100644
--- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
+++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
@@ -4,6 +4,7 @@ import {TbCursorText} from "react-icons/tb"
import {useTranslation} from "react-i18next"
import {MdCheck, MdOutlineChangeCircle, MdTextFormat} from "react-icons/md"
import {BsImage} from "react-icons/bs"
+import {AxiosError} from "axios"
import {
Checkbox,
@@ -16,12 +17,19 @@ import {
Typography,
} from "@mui/material"
import {LoadingButton} from "@mui/lab"
+import {useMutation} from "@tanstack/react-query"
import {AdminSettings} from "~/server-types"
import {StringPoolField, createPool} from "~/components"
+import {updateAdminSettings} from "~/apis"
+import {useErrorSuccessSnacks} from "~/hooks"
+import {queryClient} from "~/constants/react-query"
+import {parseFastAPIError} from "~/utils"
+import {DEFAULT_ADMIN_SETTINGS} from "~/constants/admin-settings"
export interface SettingsFormProps {
settings: AdminSettings
+ queryKey: readonly string[]
}
const DEFAULT_POOLS = createPool({
@@ -30,8 +38,9 @@ const DEFAULT_POOLS = createPool({
"0123456789": "0-9",
})
-export default function SettingsForm({settings}: SettingsFormProps) {
+export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
const {t} = useTranslation()
+ const {showSuccess, showError} = useErrorSuccessSnacks()
const validationSchema = yup.object().shape({
randomEmailIdMinLength: yup
@@ -74,9 +83,40 @@ export default function SettingsForm({settings}: SettingsFormProps) {
.label(t("routes.AdminRoute.forms.settings.allowStatistics.label")),
})
- const formik = useFormik({
+ const {mutateAsync} = useMutation>(
+ async settings => {
+ // Set values to `null` that are their defaults
+ const strippedSettings = Object.fromEntries(
+ Object.entries(settings as AdminSettings).map(([key, value]) => {
+ if (value === DEFAULT_ADMIN_SETTINGS[key as keyof AdminSettings]) {
+ return [key, null]
+ }
+
+ return [key, value]
+ }),
+ )
+
+ return updateAdminSettings(strippedSettings)
+ },
+ {
+ onError: showError,
+ onSuccess: newSettings => {
+ showSuccess(t("routes.AdminRoute.settings.successMessage"))
+
+ queryClient.setQueryData(queryKey, newSettings)
+ },
+ },
+ )
+
+ const formik = useFormik({
validationSchema,
- onSubmit: console.log,
+ onSubmit: async (values, {setErrors}) => {
+ try {
+ await mutateAsync(values)
+ } catch (error) {
+ setErrors(parseFastAPIError(error as AxiosError))
+ }
+ },
initialValues: settings,
})
From ff18b33aaf3a61ba13a9e0f629d114d3d90c1cd9 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sat, 11 Feb 2023 19:37:39 +0100
Subject: [PATCH 18/30] feat: Add Preview for admin SettingsForm.tsx
---
public/locales/en-US/translation.json | 7 ++-
src/App.tsx | 1 +
.../AliasPercentageAmount.tsx | 32 ++++++++++
.../RandomAliasGenerator.tsx | 59 +++++++++++++++++++
.../GlobalSettingsRoute/SettingsForm.tsx | 15 +++++
5 files changed, 113 insertions(+), 1 deletion(-)
create mode 100644 src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx
create mode 100644 src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx
diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json
index e069221..141d829 100644
--- a/public/locales/en-US/translation.json
+++ b/public/locales/en-US/translation.json
@@ -383,7 +383,12 @@
"settings": {
"title": "Global Settings",
"description": "Configure global settings for your instance.",
- "successMessage": "Settings have been saved successfully!"
+ "successMessage": "Settings have been saved successfully!",
+ "randomAliasesPreview": {
+ "title": "Random aliases will look like this",
+ "helperText": "This is just a preview. Those are not real aliases."
+ },
+ "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created."
}
}
},
diff --git a/src/App.tsx b/src/App.tsx
index 0e4e335..75f1d89 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -122,6 +122,7 @@ const router = createBrowserRouter([
},
{
path: "/admin/settings",
+ loader: getServerSettings,
element: ,
},
],
diff --git a/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx b/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx
new file mode 100644
index 0000000..117a1da
--- /dev/null
+++ b/src/route-widgets/GlobalSettingsRoute/AliasPercentageAmount.tsx
@@ -0,0 +1,32 @@
+import {ReactElement} from "react"
+import {useTranslation} from "react-i18next"
+
+import {Alert, Typography} from "@mui/material"
+
+export interface AliasPercentageAmountProps {
+ characters: string
+ length: number
+ percentage: number
+}
+
+export default function AliasesPercentageAmount({
+ characters,
+ length,
+ percentage,
+}: AliasPercentageAmountProps): ReactElement {
+ const {t} = useTranslation()
+
+ const amount = Math.floor(Math.pow(characters.length, length) * percentage)
+
+ return (
+
+
+ {t("routes.AdminRoute.settings.randomAliasesIncreaseExplanation", {
+ originalLength: length,
+ increasedLength: length + 1,
+ amount,
+ })}
+
+
+ )
+}
diff --git a/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx b/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx
new file mode 100644
index 0000000..37cb4b6
--- /dev/null
+++ b/src/route-widgets/GlobalSettingsRoute/RandomAliasGenerator.tsx
@@ -0,0 +1,59 @@
+import {useLoaderData} from "react-router-dom"
+import {ReactElement, useCallback, useState} from "react"
+import {useUpdateEffect} from "react-use"
+import {BiRefresh} from "react-icons/bi"
+import {useTranslation} from "react-i18next"
+
+import {Alert, FormHelperText, Grid, IconButton, Typography, useTheme} from "@mui/material"
+
+import {ServerSettings} from "~/server-types"
+
+export interface RandomAliasGeneratorProps {
+ characters: string
+ length: number
+}
+
+export default function RandomAliasGenerator({
+ characters,
+ length,
+}: RandomAliasGeneratorProps): ReactElement {
+ const serverSettings = useLoaderData() as ServerSettings
+ const {t} = useTranslation()
+ const theme = useTheme()
+
+ const generateLocal = useCallback(
+ () =>
+ Array.from({length}, () =>
+ characters.charAt(Math.floor(Math.random() * characters.length)),
+ ).join(""),
+ [characters, length],
+ )
+ const [local, setLocal] = useState(generateLocal)
+
+ const email = `${local}@${serverSettings.mailDomain}`
+
+ useUpdateEffect(() => {
+ setLocal(generateLocal())
+ }, [generateLocal])
+
+ return (
+
+
+ {t("routes.AdminRoute.settings.randomAliasesPreview.title")}
+
+
+
+ {email}
+
+
+ setLocal(generateLocal())}>
+
+
+
+
+
+ {t("routes.AdminRoute.settings.randomAliasesPreview.helperText")}
+
+
+ )
+}
diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
index f32288a..af5c423 100644
--- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
+++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
@@ -26,6 +26,8 @@ import {useErrorSuccessSnacks} from "~/hooks"
import {queryClient} from "~/constants/react-query"
import {parseFastAPIError} from "~/utils"
import {DEFAULT_ADMIN_SETTINGS} from "~/constants/admin-settings"
+import AliasesPercentageAmount from "./AliasPercentageAmount"
+import RandomAliasGenerator from "~/route-widgets/GlobalSettingsRoute/RandomAliasGenerator"
export interface SettingsFormProps {
settings: AdminSettings
@@ -212,6 +214,12 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
}
/>
+
+
+
+
+
+
Date: Sat, 11 Feb 2023 19:42:37 +0100
Subject: [PATCH 19/30] feat: Show hour unit on image proxy storage lifetime
field
---
public/locales/en-US/translation.json | 4 +++-
.../GlobalSettingsRoute/SettingsForm.tsx | 12 ++++++++++++
2 files changed, 15 insertions(+), 1 deletion(-)
diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json
index 141d829..3a98730 100644
--- a/public/locales/en-US/translation.json
+++ b/public/locales/en-US/translation.json
@@ -360,7 +360,9 @@
},
"imageProxyStorageLifeTimeInHours": {
"label": "Image proxy storage lifetime",
- "description": "The lifetime of images that are stored on the server. After this time, the image will be deleted."
+ "description": "The lifetime of images that are stored on the server in hours. After this time, the image will be deleted.",
+ "unit_one": "hour",
+ "unit_other": "hours"
},
"enableImageProxy": {
"label": "Enable image proxy",
diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
index af5c423..44a9d70 100644
--- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
+++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
@@ -355,6 +355,18 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
),
+ endAdornment: (
+
+ {t(
+ "routes.AdminRoute.forms.settings.imageProxyStorageLifeTimeInHours.unit",
+ {
+ count:
+ formik.values
+ .imageProxyStorageLifeTimeInHours || 0,
+ },
+ )}
+
+ ),
}}
/>
From a29fe864d3e0d4718bef9a33cf8e006927494050 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sat, 11 Feb 2023 19:45:40 +0100
Subject: [PATCH 20/30] fix: Pass querykey
---
src/routes/GlobalSettingsRoute.tsx | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/routes/GlobalSettingsRoute.tsx b/src/routes/GlobalSettingsRoute.tsx
index 5387e25..2b841db 100644
--- a/src/routes/GlobalSettingsRoute.tsx
+++ b/src/routes/GlobalSettingsRoute.tsx
@@ -11,7 +11,8 @@ import {DEFAULT_ADMIN_SETTINGS} from "~/constants/admin-settings"
import SettingsForm from "~/route-widgets/GlobalSettingsRoute/SettingsForm"
export default function GlobalSettingsRoute(): ReactElement {
- const query = useQuery(["get_admin_settings"], async () => {
+ const queryKey = ["get_admin_settings"]
+ const query = useQuery(queryKey, async () => {
const settings = getAdminSettings()
return _.mergeWith({}, DEFAULT_ADMIN_SETTINGS, settings, (o, s) =>
@@ -21,7 +22,7 @@ export default function GlobalSettingsRoute(): ReactElement {
return (
query={query}>
- {settings => }
+ {settings => }
)
}
From 6dd49d659e3ea4f682d6e29c23966b1382d6b179 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sat, 11 Feb 2023 20:04:24 +0100
Subject: [PATCH 21/30] feat: Add reset functionality
---
public/locales/en-US/translation.json | 3 +-
.../GlobalSettingsRoute/SettingsForm.tsx | 41 +++++++++++++------
src/server-types.ts | 14 +++----
3 files changed, 38 insertions(+), 20 deletions(-)
diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json
index 3a98730..bd8b9d4 100644
--- a/public/locales/en-US/translation.json
+++ b/public/locales/en-US/translation.json
@@ -390,7 +390,8 @@
"title": "Random aliases will look like this",
"helperText": "This is just a preview. Those are not real aliases."
},
- "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created."
+ "randomAliasesIncreaseExplanation": "Random aliases' length will be increased from {{originalLength}} to {{increasedLength}} characters after {{amount}} aliases have been created.",
+ "resetLabel": "Reset to defaults"
}
}
},
diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
index 44a9d70..d99b4d9 100644
--- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
+++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
@@ -2,7 +2,7 @@ import * as yup from "yup"
import {useFormik} from "formik"
import {TbCursorText} from "react-icons/tb"
import {useTranslation} from "react-i18next"
-import {MdCheck, MdOutlineChangeCircle, MdTextFormat} from "react-icons/md"
+import {MdCheck, MdClear, MdOutlineChangeCircle, MdTextFormat} from "react-icons/md"
import {BsImage} from "react-icons/bs"
import {AxiosError} from "axios"
@@ -122,6 +122,8 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
initialValues: settings,
})
+ // Fields will either have a value or be filled from the default values.
+ // That means we will never have a `null` value.
return (
@@ -255,9 +257,9 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
@@ -304,7 +306,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
"routes.AdminRoute.forms.settings.customEmailSuffixChars.label",
)}
name="customEmailSuffixChars"
- value={formik.values.customEmailSuffixChars}
+ value={formik.values.customEmailSuffixChars!}
onChange={formik.handleChange}
error={
formik.touched.customEmailSuffixChars &&
@@ -403,7 +405,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
@@ -431,7 +433,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
@@ -459,7 +461,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
@@ -485,7 +487,22 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
-
+
+
+ }
+ color="warning"
+ onClick={() => {
+ formik.setValues(DEFAULT_ADMIN_SETTINGS)
+ formik.submitForm()
+ }}
+ >
+ {t("routes.AdminRoute.forms.settings.resetLabel")}
+
+
Date: Sat, 11 Feb 2023 20:10:58 +0100
Subject: [PATCH 22/30] fix: field name
---
src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
index d99b4d9..f9269c1 100644
--- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
+++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
@@ -229,7 +229,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
label={t(
"routes.AdminRoute.forms.settings.randomEmailLengthIncreaseOnPercentage.label",
)}
- name="randomEmailIdLengthIncreaseOnPercentage"
+ name="randomEmailLengthIncreaseOnPercentage"
value={formik.values.randomEmailLengthIncreaseOnPercentage}
onChange={formik.handleChange}
error={
From 5e456205e961a2306defa521506edb71a22d9818 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sat, 11 Feb 2023 20:30:34 +0100
Subject: [PATCH 23/30] fix: translations
---
src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
index f9269c1..4cad614 100644
--- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
+++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
@@ -500,7 +500,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
formik.submitForm()
}}
>
- {t("routes.AdminRoute.forms.settings.resetLabel")}
+ {t("routes.AdminRoute.settings.resetLabel")}
From f0ecace4756927e1cd93dfbb8ee1d77fa3e0b043 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sat, 11 Feb 2023 20:31:30 +0100
Subject: [PATCH 24/30] fix: Checkbox will be disabled when form submitted
---
src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
index 4cad614..5207c2f 100644
--- a/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
+++ b/src/route-widgets/GlobalSettingsRoute/SettingsForm.tsx
@@ -382,6 +382,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
name="userEmailEnableDisposableEmails"
/>
}
+ disabled={formik.isSubmitting}
label={t(
"routes.AdminRoute.forms.settings.userEmailEnableDisposableEmails.label",
)}
@@ -410,6 +411,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
name="userEmailEnableOtherRelays"
/>
}
+ disabled={formik.isSubmitting}
label={t(
"routes.AdminRoute.forms.settings.userEmailEnableOtherRelays.label",
)}
@@ -438,6 +440,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
name="enableImageProxy"
/>
}
+ disabled={formik.isSubmitting}
label={t(
"routes.AdminRoute.forms.settings.enableImageProxy.label",
)}
@@ -466,6 +469,7 @@ export default function SettingsForm({settings, queryKey}: SettingsFormProps) {
name="allowStatistics"
/>
}
+ disabled={formik.isSubmitting}
label={t(
"routes.AdminRoute.forms.settings.allowStatistics.label",
)}
From b405a0817faba70adc8e2f5c99364b303fc49f3a Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sun, 12 Feb 2023 16:21:34 +0100
Subject: [PATCH 25/30] fix: Only show admin Page when user is admin; Remove
Overview section
---
src/routes/AuthenticatedRoute.tsx | 14 ++++++++------
src/routes/VerifyEmailRoute.tsx | 1 -
2 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/routes/AuthenticatedRoute.tsx b/src/routes/AuthenticatedRoute.tsx
index c7ac98a..3298168 100644
--- a/src/routes/AuthenticatedRoute.tsx
+++ b/src/routes/AuthenticatedRoute.tsx
@@ -11,15 +11,17 @@ import NavigationButton, {
NavigationSection,
} from "~/route-widgets/AuthenticateRoute/NavigationButton"
-const sections = (Object.keys(NavigationSection) as Array).filter(
- value => isNaN(Number(value)),
-)
-
export default function AuthenticatedRoute(): ReactElement {
const {t} = useTranslation()
const theme = useTheme()
+ const user = useUser()
- useUser()
+ const sections = [
+ NavigationSection.Aliases,
+ NavigationSection.Reports,
+ NavigationSection.Settings,
+ user.isAdmin && NavigationSection.Admin,
+ ].filter(value => value !== false) as NavigationSection[]
return (
@@ -50,7 +52,7 @@ export default function AuthenticatedRoute(): ReactElement {
{sections.map(key => (
-
+
))}
diff --git a/src/routes/VerifyEmailRoute.tsx b/src/routes/VerifyEmailRoute.tsx
index e143295..b5a8786 100644
--- a/src/routes/VerifyEmailRoute.tsx
+++ b/src/routes/VerifyEmailRoute.tsx
@@ -45,7 +45,6 @@ export default function VerifyEmailRoute(): ReactElement {
verifyEmail,
{
onSuccess: ({user}) => {
- setEmail("")
login(user)
navigate("/auth/complete-account")
},
From bf489d587b92aba743ad65182c803d3f0927ce1f Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sun, 12 Feb 2023 16:21:41 +0100
Subject: [PATCH 26/30] fix: Navigate to /aliases by default
---
src/hooks/use-navigate-to-next.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/hooks/use-navigate-to-next.ts b/src/hooks/use-navigate-to-next.ts
index 5dc3cdf..0346133 100644
--- a/src/hooks/use-navigate-to-next.ts
+++ b/src/hooks/use-navigate-to-next.ts
@@ -3,7 +3,7 @@ import {useCallback} from "react"
import {getNextUrl} from "~/utils"
-export default function useNavigateToNext(defaultNextUrl = "/"): () => void {
+export default function useNavigateToNext(defaultNextUrl = "/aliases"): () => void {
const navigate = useNavigate()
const location = useLocation()
From 9a24e6b3e4eb24c648e5376d5582430c813b9d3d Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sun, 12 Feb 2023 16:22:05 +0100
Subject: [PATCH 27/30] fix: Navigate to /aliases after login
---
src/routes/LoginRoute.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/routes/LoginRoute.tsx b/src/routes/LoginRoute.tsx
index 36a15cc..2016bb6 100644
--- a/src/routes/LoginRoute.tsx
+++ b/src/routes/LoginRoute.tsx
@@ -25,7 +25,7 @@ export default function LoginRoute(): ReactElement {
if (user?.encryptedPassword) {
navigate("/enter-password")
} else {
- navigate("/")
+ navigate("/aliases")
}
}, [user, navigate])
From 30ccdbce1323653a438004d8a000ad2c286886b6 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sun, 12 Feb 2023 16:27:26 +0100
Subject: [PATCH 28/30] fix: Clean up user on sign up page when logged in
---
src/components/AuthContext/AuthContextProvider.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/components/AuthContext/AuthContextProvider.tsx b/src/components/AuthContext/AuthContextProvider.tsx
index 777e493..9ca2205 100644
--- a/src/components/AuthContext/AuthContextProvider.tsx
+++ b/src/components/AuthContext/AuthContextProvider.tsx
@@ -35,6 +35,7 @@ export default function AuthContextProvider({children}: AuthContextProviderProps
user as User,
)
const logout = useCallback(() => {
+ localStorage.removeItem("signup-form-state-email")
logoutMasterPassword()
setUser(null)
}, [logoutMasterPassword])
From 652837838b1c659d09db21baaa9f8d4207c9c426 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sun, 12 Feb 2023 16:38:56 +0100
Subject: [PATCH 29/30] feat: Add EmptyStateScreen.tsx for reserved aliases
---
public/locales/en-US/translation.json | 6 +-
.../ReservedAliasesRoute/EmptyStateScreen.tsx | 37 ++++++++++
src/routes/ReservedAliasesRoute.tsx | 71 +++++++++++--------
3 files changed, 84 insertions(+), 30 deletions(-)
create mode 100644 src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx
diff --git a/public/locales/en-US/translation.json b/public/locales/en-US/translation.json
index bd8b9d4..f67df95 100644
--- a/public/locales/en-US/translation.json
+++ b/public/locales/en-US/translation.json
@@ -295,7 +295,11 @@
}
},
"userAmount_one": "Forwards to one user",
- "userAmount_other": "Forwards to {{count}} users"
+ "userAmount_other": "Forwards to {{count}} users",
+ "emptyState": {
+ "title": "Create your first reserved alias",
+ "description": "Reserved aliases are aliases that will be forwarded to selected admin users. This is useful if you want to create aliases that are meant to be public, like contact@example.com or hello@example.com."
+ }
},
"ReservedAliasDetailRoute": {
"title": "Reserved Alias Details",
diff --git a/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx b/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx
new file mode 100644
index 0000000..ee300e9
--- /dev/null
+++ b/src/route-widgets/ReservedAliasesRoute/EmptyStateScreen.tsx
@@ -0,0 +1,37 @@
+import {ReactElement} from "react"
+import {useTranslation} from "react-i18next"
+
+import {Container, Grid, Typography} from "@mui/material"
+import {BsStarFill} from "react-icons/bs"
+
+export default function EmptyStateScreen(): ReactElement {
+ const {t} = useTranslation()
+
+ return (
+
+
+
+
+ {t("routes.ReservedAliasesRoute.emptyState.title")}
+
+
+
+
+
+
+
+ {t("routes.ReservedAliasesRoute.emptyState.description")}
+
+
+
+
+ )
+}
diff --git a/src/routes/ReservedAliasesRoute.tsx b/src/routes/ReservedAliasesRoute.tsx
index 493f141..4b6ca4a 100644
--- a/src/routes/ReservedAliasesRoute.tsx
+++ b/src/routes/ReservedAliasesRoute.tsx
@@ -18,7 +18,8 @@ import {useQuery} from "@tanstack/react-query"
import {PaginationResult, ReservedAlias} from "~/server-types"
import {getReservedAliases} from "~/apis"
-import {QueryResult, SimplePage} from "~/components"
+import {NoSearchResults, QueryResult, SimplePage} from "~/components"
+import EmptyStateScreen from "~/route-widgets/ReservedAliasesRoute/EmptyStateScreen"
export default function ReservedAliasesRoute(): ReactElement {
const {t} = useTranslation()
@@ -33,8 +34,10 @@ export default function ReservedAliasesRoute(): ReactElement {
query: queryValue,
}),
{
- onSuccess: () => {
- setShowSearch(true)
+ onSuccess: ({items}) => {
+ if (items.length) {
+ setShowSearch(true)
+ }
},
},
)
@@ -81,32 +84,42 @@ export default function ReservedAliasesRoute(): ReactElement {
}
>
, AxiosError> query={query}>
- {({items: aliases}) => (
-
- {aliases.map(alias => (
-
-
- {alias.local}
- @{alias.domain}
- >
- }
- secondary={t("routes.ReservedAliasesRoute.userAmount", {
- count: alias.users.length,
- })}
- />
-
-
-
-
- ))}
-
- )}
+ {({items: aliases}) => {
+ if (aliases.length === 0) {
+ if (searchValue === "") {
+ return
+ } else {
+ return
+ }
+ }
+
+ return (
+
+ {aliases.map(alias => (
+
+
+ {alias.local}
+ @{alias.domain}
+ >
+ }
+ secondary={t("routes.ReservedAliasesRoute.userAmount", {
+ count: alias.users.length,
+ })}
+ />
+
+
+
+
+ ))}
+
+ )
+ }}
)
From 880f30ef879bef50bbbc80f8bb38b2cffe696949 Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Sun, 12 Feb 2023 17:19:53 +0100
Subject: [PATCH 30/30] fix: User not being authenticated but on
AuthenticatedRoute.tsx
---
src/routes/AuthenticatedRoute.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/routes/AuthenticatedRoute.tsx b/src/routes/AuthenticatedRoute.tsx
index 3298168..f04ca7c 100644
--- a/src/routes/AuthenticatedRoute.tsx
+++ b/src/routes/AuthenticatedRoute.tsx
@@ -20,7 +20,7 @@ export default function AuthenticatedRoute(): ReactElement {
NavigationSection.Aliases,
NavigationSection.Reports,
NavigationSection.Settings,
- user.isAdmin && NavigationSection.Admin,
+ user?.isAdmin && NavigationSection.Admin,
].filter(value => value !== false) as NavigationSection[]
return (