From 53589b941922918a7c3240417b0ae2b3dfe02dbe Mon Sep 17 00:00:00 2001
From: Myzel394 <50424412+Myzel394@users.noreply.github.com>
Date: Thu, 20 Oct 2022 21:48:49 +0200
Subject: [PATCH] added SettingsRoute.tsx
---
src/App.tsx | 5 +
src/AuthContext/AuthContext.ts | 4 +
src/AuthContext/AuthContextProvider.tsx | 12 +
src/apis/index.ts | 2 +
src/apis/update-preferences.ts | 28 ++
src/components/ErrorSnack.tsx | 27 ++
src/components/SuccessSnack.tsx | 29 ++
src/components/index.ts | 4 +
.../SettingsRoute/AliasesPreferencesForm.tsx | 359 ++++++++++++++++++
src/routes/AuthenticatedRoute.tsx | 61 +--
src/routes/SettingsRoute.tsx | 20 +
11 files changed, 528 insertions(+), 23 deletions(-)
create mode 100644 src/apis/update-preferences.ts
create mode 100644 src/components/ErrorSnack.tsx
create mode 100644 src/components/SuccessSnack.tsx
create mode 100644 src/route-widgets/SettingsRoute/AliasesPreferencesForm.tsx
create mode 100644 src/routes/SettingsRoute.tsx
diff --git a/src/App.tsx b/src/App.tsx
index 8567bc5..1f8f2c7 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -14,6 +14,7 @@ import AuthenticatedRoute from "~/routes/AuthenticatedRoute"
import CompleteAccountRoute from "~/routes/CompleteAccountRoute"
import LoginRoute from "~/routes/LoginRoute"
import RootRoute from "~/routes/Root"
+import SettingsRoute from "~/routes/SettingsRoute"
import SignupRoute from "~/routes/SignupRoute"
import VerifyEmailRoute from "~/routes/VerifyEmailRoute"
@@ -56,6 +57,10 @@ const router = createBrowserRouter([
path: "/aliases",
element: ,
},
+ {
+ path: "/settings",
+ element: ,
+ },
],
},
],
diff --git a/src/AuthContext/AuthContext.ts b/src/AuthContext/AuthContext.ts
index 86b7b5c..0c66e78 100644
--- a/src/AuthContext/AuthContext.ts
+++ b/src/AuthContext/AuthContext.ts
@@ -10,6 +10,7 @@ interface AuthContextTypeBase {
_decryptContent: (content: string) => string
_encryptContent: (content: string) => string
_setDecryptionPassword: (decryptionPassword: string) => void
+ _updateUser: (user: ServerUser | User) => void
}
interface AuthContextTypeAuthenticated {
@@ -44,6 +45,9 @@ const AuthContext = createContext({
_setDecryptionPassword: () => {
throw new Error("_setMasterDecryptionPassword() not implemented")
},
+ _updateUser: () => {
+ throw new Error("_updateUser() not implemented")
+ },
})
export default AuthContext
diff --git a/src/AuthContext/AuthContextProvider.tsx b/src/AuthContext/AuthContextProvider.tsx
index c5bb6b8..e02f2de 100644
--- a/src/AuthContext/AuthContextProvider.tsx
+++ b/src/AuthContext/AuthContextProvider.tsx
@@ -89,6 +89,17 @@ export default function AuthContextProvider({
[masterPassword, decryptContent],
)
+ const updateUser = useCallback(
+ async (newUser: ServerUser | User) => {
+ if (user === null) {
+ throw new Error("Can't update user when user is null.")
+ }
+
+ setUser(newUser)
+ },
+ [user],
+ )
+
const {mutateAsync: refresh} = useMutation<
RefreshTokenResult,
AxiosError,
@@ -106,6 +117,7 @@ export default function AuthContextProvider({
_encryptContent: encryptContent,
_decryptContent: decryptContent,
_setDecryptionPassword: setDecryptionPassword,
+ _updateUser: updateUser,
}),
[refresh, login, logout],
)
diff --git a/src/apis/index.ts b/src/apis/index.ts
index 2381b03..e77ab4e 100644
--- a/src/apis/index.ts
+++ b/src/apis/index.ts
@@ -24,3 +24,5 @@ export * from "./verify-login-with-email"
export {default as verifyLoginWithEmail} from "./verify-login-with-email"
export * from "./resend-email-login-code"
export {default as resendEmailLoginCode} from "./resend-email-login-code"
+export * from "./update-preferences"
+export {default as updatePreferences} from "./update-preferences"
diff --git a/src/apis/update-preferences.ts b/src/apis/update-preferences.ts
new file mode 100644
index 0000000..a07197f
--- /dev/null
+++ b/src/apis/update-preferences.ts
@@ -0,0 +1,28 @@
+import {
+ ImageProxyFormatType,
+ ProxyUserAgentType,
+ SimpleDetailResponse,
+} from "~/server-types"
+import {client} from "~/constants/axios-client"
+
+export interface UpdatePreferencesData {
+ aliasRemoveTrackers?: boolean
+ aliasCreateMailReport?: boolean
+ aliasProxyImages?: boolean
+ aliasImageProxyFormat?: ImageProxyFormatType
+ aliasImageProxyUserAgent?: ProxyUserAgentType
+}
+
+export default async function updatePreferences(
+ updateData: UpdatePreferencesData,
+): Promise {
+ const {data} = await client.patch(
+ `${import.meta.env.VITE_SERVER_BASE_URL}/preferences`,
+ updateData,
+ {
+ withCredentials: true,
+ },
+ )
+
+ return data
+}
diff --git a/src/components/ErrorSnack.tsx b/src/components/ErrorSnack.tsx
new file mode 100644
index 0000000..f4d25d2
--- /dev/null
+++ b/src/components/ErrorSnack.tsx
@@ -0,0 +1,27 @@
+import React, {ReactElement, useEffect, useState} from "react"
+
+import {Alert, Snackbar} from "@mui/material"
+
+export interface ErrorSnackProps {
+ message?: string | null | false
+}
+
+export default function ErrorSnack({message}: ErrorSnackProps): ReactElement {
+ const [open, setOpen] = useState(true)
+
+ useEffect(() => {
+ setOpen(Boolean(message))
+ }, [message])
+
+ return (
+ setOpen(false)}
+ >
+
+ {message}
+
+
+ )
+}
diff --git a/src/components/SuccessSnack.tsx b/src/components/SuccessSnack.tsx
new file mode 100644
index 0000000..b277424
--- /dev/null
+++ b/src/components/SuccessSnack.tsx
@@ -0,0 +1,29 @@
+import React, {ReactElement, useEffect, useState} from "react"
+
+import {Alert, Snackbar} from "@mui/material"
+
+export interface SuccessSnackProps {
+ message?: string | null
+}
+
+export default function SuccessSnack({
+ message,
+}: SuccessSnackProps): ReactElement {
+ const [open, setOpen] = useState(true)
+
+ useEffect(() => {
+ setOpen(Boolean(message))
+ }, [message])
+
+ return (
+ setOpen(false)}
+ >
+
+ {message}
+
+
+ )
+}
diff --git a/src/components/index.ts b/src/components/index.ts
index a27bd0d..1caae5a 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -12,3 +12,7 @@ export * from "./MutationStatusSnackbar"
export {default as MutationStatusSnackbar} from "./MutationStatusSnackbar"
export * from "./TimedButton"
export {default as TimedButton} from "./TimedButton"
+export * from "./ErrorSnack"
+export {default as ErrorSnack} from "./ErrorSnack"
+export * from "./SuccessSnack"
+export {default as SuccessSnack} from "./SuccessSnack"
diff --git a/src/route-widgets/SettingsRoute/AliasesPreferencesForm.tsx b/src/route-widgets/SettingsRoute/AliasesPreferencesForm.tsx
new file mode 100644
index 0000000..2b439cd
--- /dev/null
+++ b/src/route-widgets/SettingsRoute/AliasesPreferencesForm.tsx
@@ -0,0 +1,359 @@
+import * as yup from "yup"
+import {AxiosError} from "axios"
+import {useFormik} from "formik"
+import {MdCheckCircle, MdImage} from "react-icons/md"
+import React, {ReactElement, useContext} from "react"
+
+import {useMutation} from "@tanstack/react-query"
+import {
+ Checkbox,
+ FormControlLabel,
+ FormGroup,
+ FormHelperText,
+ Grid,
+ InputAdornment,
+ MenuItem,
+ TextField,
+ Typography,
+} from "@mui/material"
+import {LoadingButton} from "@mui/lab"
+
+import {
+ ImageProxyFormatType,
+ ProxyUserAgentType,
+ SimpleDetailResponse,
+} from "~/server-types"
+import {UpdatePreferencesData, updatePreferences} from "~/apis"
+import {useUser} from "~/hooks"
+import {parseFastapiError} from "~/utils"
+import {SuccessSnack} from "~/components"
+import AuthContext from "~/AuthContext/AuthContext"
+import ErrorSnack from "~/components/ErrorSnack"
+
+interface Form {
+ removeTrackers: boolean
+ createMailReport: boolean
+ proxyImages: boolean
+ imageProxyFormat: ImageProxyFormatType
+ imageProxyUserAgent: ProxyUserAgentType
+
+ detail?: string
+}
+
+const SCHEMA = yup.object().shape({
+ removeTrackers: yup.boolean(),
+ createMailReport: yup.boolean(),
+ proxyImages: yup.boolean(),
+ imageProxyFormat: yup
+ .mixed()
+ .oneOf(Object.values(ImageProxyFormatType))
+ .required(),
+ imageProxyUserAgent: yup
+ .mixed()
+ .oneOf(Object.values(ProxyUserAgentType))
+ .required(),
+})
+
+const IMAGE_PROXY_FORMAT_TYPE_NAME_MAP: Record = {
+ [ImageProxyFormatType.JPEG]: "JPEG",
+ [ImageProxyFormatType.PNG]: "PNG",
+ [ImageProxyFormatType.WEBP]: "WebP",
+}
+
+const IMAGE_PROXY_USER_AGENT_TYPE_NAME_MAP: Record =
+ {
+ [ProxyUserAgentType.APPLE_MAIL]: "Apple Mail",
+ [ProxyUserAgentType.GOOGLE_MAIL]: "Google Mail",
+ [ProxyUserAgentType.CHROME]: "Chrome Browser",
+ [ProxyUserAgentType.FIREFOX]: "Firefox Browser",
+ [ProxyUserAgentType.OUTLOOK_MACOS]: "Outlook / MacOS",
+ [ProxyUserAgentType.OUTLOOK_WINDOWS]: "Outlook / Windows",
+ }
+
+export default function AliasesPreferencesForm(): ReactElement {
+ const {_updateUser} = useContext(AuthContext)
+ const user = useUser()
+ const {mutateAsync, data} = useMutation<
+ SimpleDetailResponse,
+ AxiosError,
+ UpdatePreferencesData
+ >(updatePreferences, {
+ onSuccess: (_, values) => {
+ const newUser = {
+ ...user,
+ preferences: {
+ ...user.preferences,
+ ...values,
+ },
+ }
+
+ _updateUser(newUser)
+ },
+ })
+ const formik = useFormik