improved snacks

This commit is contained in:
Myzel394 2022-11-01 21:13:18 +01:00
parent 8616d4085a
commit 2cb17e5550
14 changed files with 477 additions and 454 deletions

View File

@ -32,6 +32,7 @@
"immutability-helper": "^3.1.1",
"in-milliseconds": "^1.2.0",
"in-seconds": "^1.2.0",
"notistack": "^2.0.8",
"openpgp": "^5.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",

View File

@ -1,12 +1,13 @@
import {RouterProvider, createBrowserRouter} from "react-router-dom"
import {SnackbarProvider} from "notistack"
import React, {ReactElement} from "react"
import {QueryClientProvider} from "@tanstack/react-query"
import {CssBaseline, ThemeProvider} from "@mui/material"
import {queryClient} from "~/constants/react-query"
import {lightTheme} from "~/constants/themes"
import {getServerSettings} from "~/apis"
import {lightTheme} from "~/constants/themes"
import AliasDetailRoute from "~/routes/AliasDetailRoute"
import AliasesRoute from "~/routes/AliasesRoute"
import AuthContextProvider from "~/AuthContext/AuthContextProvider"
@ -96,10 +97,12 @@ export default function App(): ReactElement {
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={lightTheme}>
<SnackbarProvider>
<AuthContextProvider>
<CssBaseline />
<RouterProvider router={router} />
</AuthContextProvider>
</SnackbarProvider>
</ThemeProvider>
</QueryClientProvider>
</React.StrictMode>

View File

@ -1,5 +1,7 @@
import {FormikContextType} from "formik"
import {useContext, useEffect} from "react"
import {useContext} from "react"
import {useDeepCompareEffect} from "react-use"
import deepEqual from "deep-equal"
import LockNavigationContext from "./LockNavigationContext"
@ -12,16 +14,14 @@ export default function FormikAutoLockNavigation({
}: LockNavigationContextProviderProps): null {
const {lock, release} = useContext(LockNavigationContext)
const valuesStringified = JSON.stringify(formik.values)
const initialValuesStringified = JSON.stringify(formik.initialValues)
useEffect(() => {
if (valuesStringified !== initialValuesStringified) {
// TODO: Not working yet
useDeepCompareEffect(() => {
if (!deepEqual(formik.values, formik.initialValues)) {
lock()
} else {
release()
}
}, [lock, release, valuesStringified, initialValuesStringified])
}, [lock, release, formik.values, formik.initialValues])
return null
}

View File

@ -12,3 +12,5 @@ export const DEFAULT_ALIAS_NOTE: AliasNote = {
websites: [],
},
}
export const ERROR_SNACKBAR_SHOW_DURATION = 5000
export const SUCCESS_SNACKBAR_SHOW_DURATION = 2000

View File

@ -10,3 +10,5 @@ export * from "./use-ui-state"
export {default as useUIState} from "./use-ui-state"
export * from "./use-navigate-to-next"
export {default as useNavigateToNext} from "./use-navigate-to-next"
export * from "./use-error-success-snacks"
export {default as useErrorSuccessSnacks} from "./use-error-success-snacks"

View File

@ -0,0 +1,48 @@
import {AxiosError} from "axios"
import {useRef} from "react"
import {SnackbarKey, useSnackbar} from "notistack"
import {useTranslation} from "react-i18next"
import {ERROR_SNACKBAR_SHOW_DURATION, SUCCESS_SNACKBAR_SHOW_DURATION} from "~/constants/values"
import {parseFastAPIError} from "~/utils"
export interface UseErrorSuccessSnacksResult {
showSuccess: (message: string) => void
showError: (error: Error) => void
}
export default function useErrorSuccessSnacks(): UseErrorSuccessSnacksResult {
const {t} = useTranslation()
const {enqueueSnackbar, closeSnackbar} = useSnackbar()
const $errorSnackbarKey = useRef<SnackbarKey | null>(null)
const showSuccess = (message: string) => {
if ($errorSnackbarKey.current) {
closeSnackbar($errorSnackbarKey.current)
$errorSnackbarKey.current = null
}
enqueueSnackbar(message, {
variant: "success",
autoHideDuration: SUCCESS_SNACKBAR_SHOW_DURATION,
})
}
const showError = (error: Error) => {
const parsedError = parseFastAPIError(error as AxiosError)
if ("detail" in parsedError) {
$errorSnackbarKey.current = enqueueSnackbar(
parsedError.detail || t("general.defaultError"),
{
variant: "error",
autoHideDuration: ERROR_SNACKBAR_SHOW_DURATION,
},
)
}
}
return {
showSuccess,
showError,
}
}

View File

@ -1,19 +1,16 @@
import {useParams} from "react-router"
import {ReactElement, useContext, useState} from "react"
import {ReactElement, useContext} from "react"
import {useTranslation} from "react-i18next"
import {MdContentCopy} from "react-icons/md"
import {useSnackbar} from "notistack"
import copy from "copy-to-clipboard"
import {Button, Grid} from "@mui/material"
import {
AliasTypeIndicator,
DecryptionPasswordMissingAlert,
SimplePageBuilder,
SuccessSnack,
} from "~/components"
import {AliasTypeIndicator, DecryptionPasswordMissingAlert, SimplePageBuilder} from "~/components"
import {Alias, DecryptedAlias} from "~/server-types"
import {useUIState} from "~/hooks"
import {SUCCESS_SNACKBAR_SHOW_DURATION} from "~/constants/values"
import AliasNotesForm from "~/route-widgets/AliasDetailRoute/AliasNotesForm"
import AliasPreferencesForm from "~/route-widgets/AliasDetailRoute/AliasPreferencesForm"
import AuthContext, {EncryptionStatus} from "~/AuthContext/AuthContext"
@ -25,15 +22,14 @@ export interface AliasDetailsProps {
export default function AliasDetails({alias: aliasValue}: AliasDetailsProps): ReactElement {
const {t} = useTranslation()
const {enqueueSnackbar} = useSnackbar()
const params = useParams()
const {encryptionStatus} = useContext(AuthContext)
const address = atob(params.addressInBase64 as string)
const [aliasUIState, setAliasUIState] = useUIState<Alias | DecryptedAlias>(aliasValue)
const [hasCopiedToClipboard, setHasCopiedToClipboard] = useState<boolean>(false)
return (
<>
<SimplePageBuilder.MultipleSections>
{[
<Grid key="basic" container spacing={1} direction="row" alignItems="center">
@ -47,7 +43,14 @@ export default function AliasDetails({alias: aliasValue}: AliasDetailsProps): Re
color="inherit"
onClick={() => {
copy(address)
setHasCopiedToClipboard(true)
enqueueSnackbar(
t("relations.alias.mutations.success.addressCopiedToClipboard"),
{
variant: "success",
autoHideDuration: SUCCESS_SNACKBAR_SHOW_DURATION,
},
)
}}
sx={{textTransform: "none", fontWeight: "normal"}}
>
@ -81,13 +84,5 @@ export default function AliasDetails({alias: aliasValue}: AliasDetailsProps): Re
</SimplePageBuilder.Section>,
]}
</SimplePageBuilder.MultipleSections>
<SuccessSnack
onClose={() => setHasCopiedToClipboard(false)}
message={
hasCopiedToClipboard &&
t("relations.alias.mutations.success.addressCopiedToClipboard")
}
/>
</>
)
}

View File

@ -6,6 +6,7 @@ import {MdCheckCircle, MdEditCalendar} from "react-icons/md"
import {RiStickyNoteFill} from "react-icons/ri"
import {FieldArray, FormikProvider, useFormik} from "formik"
import {FaPen} from "react-icons/fa"
import {useTranslation} from "react-i18next"
import deepEqual from "deep-equal"
import format from "date-fns/format"
import update from "immutability-helper"
@ -26,12 +27,13 @@ import {
} from "@mui/material"
import {parseFastAPIError} from "~/utils"
import {ErrorSnack, FaviconImage, SimpleOverlayInformation, SuccessSnack} from "~/components"
import {FaviconImage, SimpleOverlayInformation} from "~/components"
import {Alias, AliasNote, DecryptedAlias} from "~/server-types"
import {UpdateAliasData, updateAlias} from "~/apis"
import {useTranslation} from "react-i18next"
import {useErrorSuccessSnacks} from "~/hooks"
import AddWebsiteField from "~/route-widgets/AliasDetailRoute/AddWebsiteField"
import AuthContext from "~/AuthContext/AuthContext"
import FormikAutoLockNavigation from "~/LockNavigationContext/FormikAutoLockNavigation"
import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes"
export interface AliasNotesFormProps {
@ -60,8 +62,9 @@ const SCHEMA = yup.object().shape({
export default function AliasNotesForm({id, notes, onChanged}: AliasNotesFormProps): ReactElement {
const {t} = useTranslation()
const {showError, showSuccess} = useErrorSuccessSnacks()
const {_encryptUsingMasterPassword, _decryptUsingMasterPassword} = useContext(AuthContext)
const {mutateAsync, isSuccess} = useMutation<Alias, AxiosError, UpdateAliasData>(
const {mutateAsync} = useMutation<Alias, AxiosError, UpdateAliasData>(
values => updateAlias(id, values),
{
onSuccess: newAlias => {
@ -70,8 +73,11 @@ export default function AliasNotesForm({id, notes, onChanged}: AliasNotesFormPro
_decryptUsingMasterPassword,
)
showSuccess(t("relations.alias.mutations.success.notesUpdated"))
onChanged(newAlias as any as DecryptedAlias)
},
onError: showError,
},
)
const initialValues = useMemo(
@ -287,10 +293,7 @@ export default function AliasNotesForm({id, notes, onChanged}: AliasNotesFormPro
</Grid>
</Grid>
</form>
<ErrorSnack message={formik.errors.detail} />
<SuccessSnack
message={isSuccess && t("relations.alias.mutations.success.notesUpdated")}
/>
<FormikAutoLockNavigation formik={formik} />
</>
)
}

View File

@ -1,5 +1,5 @@
import * as yup from "yup"
import {ReactElement} from "react"
import {ReactElement, useContext} from "react"
import {BsImage, BsShieldShaded} from "react-icons/bs"
import {useFormik} from "formik"
import {FaFile} from "react-icons/fa"
@ -15,14 +15,16 @@ import Icon from "@mdi/react"
import {Alias, DecryptedAlias, ImageProxyFormatType, ProxyUserAgentType} from "~/server-types"
import {UpdateAliasData, updateAlias} from "~/apis"
import {ErrorSnack, SuccessSnack} from "~/components"
import {parseFastAPIError} from "~/utils"
import {
IMAGE_PROXY_FORMAT_TYPE_NAME_MAP,
IMAGE_PROXY_USER_AGENT_TYPE_NAME_MAP,
} from "~/constants/enum-mappings"
import {useErrorSuccessSnacks} from "~/hooks"
import AuthContext from "~/AuthContext/AuthContext"
import FormikAutoLockNavigation from "~/LockNavigationContext/FormikAutoLockNavigation"
import SelectField from "~/route-widgets/SettingsRoute/SelectField"
import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes"
export interface AliasPreferencesFormProps {
alias: Alias | DecryptedAlias
@ -45,6 +47,8 @@ export default function AliasPreferencesForm({
onChanged,
}: AliasPreferencesFormProps): ReactElement {
const {t} = useTranslation()
const {showSuccess, showError} = useErrorSuccessSnacks()
const {_decryptUsingMasterPassword} = useContext(AuthContext)
const SCHEMA = yup.object().shape({
removeTrackers: yup
.mixed<boolean | null>()
@ -64,10 +68,19 @@ export default function AliasPreferencesForm({
.oneOf([null, ...Object.values(ProxyUserAgentType)])
.label(t("relations.alias.settings.imageProxyUserAgent.label")),
})
const {mutateAsync, isSuccess} = useMutation<Alias, AxiosError, UpdateAliasData>(
const {mutateAsync} = useMutation<Alias, AxiosError, UpdateAliasData>(
data => updateAlias(alias.id, data),
{
onSuccess: onChanged,
onSuccess: alias => {
showSuccess(t("relations.alias.mutations.success.aliasUpdated"))
;(alias as any as DecryptedAlias).notes = decryptAliasNotes(
alias.encryptedNotes,
_decryptUsingMasterPassword,
)
onChanged(alias)
},
onError: showError,
},
)
const formik = useFormik<Form>({
@ -186,10 +199,6 @@ export default function AliasPreferencesForm({
</Grid>
</form>
<FormikAutoLockNavigation formik={formik} />
<ErrorSnack message={formik.errors.detail} />
<SuccessSnack
message={isSuccess && t("relations.alias.mutations.success.aliasUpdated")}
/>
</>
)
}

View File

@ -1,4 +1,4 @@
import {ReactElement, useContext, useState} from "react"
import {ReactElement, useContext} from "react"
import {AxiosError} from "axios"
import {useTranslation} from "react-i18next"
@ -7,9 +7,7 @@ import {useMutation} from "@tanstack/react-query"
import {Alias, DecryptedAlias} from "~/server-types"
import {UpdateAliasData, updateAlias} from "~/apis"
import {parseFastAPIError} from "~/utils"
import {ErrorSnack, SuccessSnack} from "~/components"
import {useUIState} from "~/hooks"
import {useErrorSuccessSnacks, useUIState} from "~/hooks"
import AuthContext, {EncryptionStatus} from "~/AuthContext/AuthContext"
import decryptAliasNotes from "~/apis/helpers/decrypt-alias-notes"
@ -26,19 +24,14 @@ export default function ChangeAliasActivationStatusSwitch({
onChanged,
}: ChangeAliasActivationStatusSwitchProps): ReactElement {
const {t} = useTranslation()
const {_decryptUsingMasterPassword, encryptionStatus} =
useContext(AuthContext)
const {showError, showSuccess} = useErrorSuccessSnacks()
const {_decryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext)
const [isActiveUIState, setIsActiveUIState] = useUIState<boolean>(isActive)
const [successMessage, setSuccessMessage] = useState<string>("")
const [errorMessage, setErrorMessage] = useState<string>("")
const {mutateAsync, isLoading} = useMutation<
Alias,
AxiosError,
UpdateAliasData
>(values => updateAlias(id, values), {
const {mutateAsync, isLoading} = useMutation<Alias, AxiosError, UpdateAliasData>(
values => updateAlias(id, values),
{
onSuccess: newAlias => {
if (encryptionStatus === EncryptionStatus.Available) {
;(newAlias as any as DecryptedAlias).notes = decryptAliasNotes(
@ -49,12 +42,11 @@ export default function ChangeAliasActivationStatusSwitch({
onChanged(newAlias)
},
onError: error =>
setErrorMessage(parseFastAPIError(error).detail as string),
})
onError: showError,
},
)
return (
<>
<Switch
checked={isActiveUIState}
disabled={isActiveUIState === null || isLoading}
@ -66,24 +58,13 @@ export default function ChangeAliasActivationStatusSwitch({
isActive: !isActiveUIState,
})
if (!isActiveUIState) {
setSuccessMessage(
t(
"relations.alias.mutations.success.aliasChangedToEnabled",
) as string,
showSuccess(
isActiveUIState
? t("relations.alias.mutations.success.aliasChangedToDisabled")
: t("relations.alias.mutations.success.aliasChangedToEnabled"),
)
} else {
setSuccessMessage(
t(
"relations.alias.mutations.success.aliasChangedToDisabled",
) as string,
)
}
} catch {}
}}
/>
<SuccessSnack message={successMessage} />
<ErrorSnack message={errorMessage} />
</>
)
}

View File

@ -1,4 +1,4 @@
import {ReactElement, useState} from "react"
import {ReactElement} from "react"
import {useTranslation} from "react-i18next"
import {useKeyPress} from "react-use"
import {MdContentCopy} from "react-icons/md"
@ -13,9 +13,11 @@ import {
ListItemText,
} from "@mui/material"
import {AliasTypeIndicator, SuccessSnack} from "~/components"
import {AliasTypeIndicator} from "~/components"
import {AliasList} from "~/server-types"
import {useUIState} from "~/hooks"
import {useSnackbar} from "notistack"
import {SUCCESS_SNACKBAR_SHOW_DURATION} from "~/constants/values"
import CreateAliasButton from "~/route-widgets/AliasesRoute/CreateAliasButton"
export interface AliasesDetailsProps {
@ -26,13 +28,12 @@ const getAddress = (alias: AliasList): string => `${alias.local}@${alias.domain}
export default function AliasesDetails({aliases}: AliasesDetailsProps): ReactElement {
const {t} = useTranslation()
const {enqueueSnackbar} = useSnackbar()
const [isInCopyAddressMode] = useKeyPress("Control")
const [aliasesUIState, setAliasesUIState] = useUIState<AliasList[]>(aliases)
const [hasCopiedToClipboard, setHasCopiedToClipboard] = useState<boolean>(false)
return (
<>
<Grid container spacing={4} direction="column">
<Grid item>
<List>
@ -43,8 +44,18 @@ export default function AliasesDetails({aliases}: AliasesDetailsProps): ReactEle
if (isInCopyAddressMode) {
event.preventDefault()
event.stopPropagation()
copy(getAddress(alias))
setHasCopiedToClipboard(true)
enqueueSnackbar(
t(
"relations.alias.mutations.success.addressCopiedToClipboard",
),
{
variant: "success",
autoHideDuration: SUCCESS_SNACKBAR_SHOW_DURATION,
},
)
}
}}
href={`/aliases/${btoa(getAddress(alias))}`}
@ -70,13 +81,5 @@ export default function AliasesDetails({aliases}: AliasesDetailsProps): ReactEle
/>
</Grid>
</Grid>
<SuccessSnack
onClose={() => setHasCopiedToClipboard(false)}
message={
hasCopiedToClipboard &&
t("relations.alias.mutations.success.addressCopiedToClipboard")
}
/>
</>
)
}

View File

@ -1,8 +1,10 @@
import {ReactElement, useContext, useState} from "react"
import {ReactElement, useContext, useRef, useState} from "react"
import {MdArrowDropDown} from "react-icons/md"
import {BsArrowClockwise} from "react-icons/bs"
import {FaPen} from "react-icons/fa"
import {AxiosError} from "axios"
import {useTranslation} from "react-i18next"
import {SnackbarKey} from "notistack"
import update from "immutability-helper"
import {
@ -18,10 +20,8 @@ import {useMutation} from "@tanstack/react-query"
import {CreateAliasData, createAlias} from "~/apis"
import {Alias, AliasType} from "~/server-types"
import {parseFastAPIError} from "~/utils"
import {ErrorSnack, SuccessSnack} from "~/components"
import {DEFAULT_ALIAS_NOTE} from "~/constants/values"
import {useTranslation} from "react-i18next"
import {useErrorSuccessSnacks} from "~/hooks"
import AuthContext, {EncryptionStatus} from "~/AuthContext/AuthContext"
import CustomAliasDialog from "~/route-widgets/AliasesRoute/CustomAliasDialog"
@ -29,20 +29,14 @@ export interface CreateAliasButtonProps {
onCreated: (alias: Alias) => void
}
export default function CreateAliasButton({
onCreated,
}: CreateAliasButtonProps): ReactElement {
export default function CreateAliasButton({onCreated}: CreateAliasButtonProps): ReactElement {
const {t} = useTranslation()
const {_encryptUsingMasterPassword, encryptionStatus} =
useContext(AuthContext)
const {showSuccess, showError} = useErrorSuccessSnacks()
const {_encryptUsingMasterPassword, encryptionStatus} = useContext(AuthContext)
const [errorMessage, setErrorMessage] = useState<string>("")
const $errorSnackbarId = useRef<SnackbarKey | null>(null)
const {mutateAsync, isLoading, isSuccess} = useMutation<
Alias,
AxiosError,
CreateAliasData
>(
const {mutateAsync, isLoading, isSuccess} = useMutation<Alias, AxiosError, CreateAliasData>(
async values => {
if (encryptionStatus === EncryptionStatus.Available) {
values.encryptedNotes = await _encryptUsingMasterPassword(
@ -61,14 +55,15 @@ export default function CreateAliasButton({
return createAlias(values)
},
{
onSuccess: onCreated,
onError: error =>
setErrorMessage(parseFastAPIError(error).detail as string),
onSuccess: alias => {
onCreated(alias)
showSuccess(t("relations.alias.mutations.success.aliasCreation"))
},
onError: showError,
},
)
const [showCustomCreateDialog, setShowCustomCreateDialog] =
useState<boolean>(false)
const [showCustomCreateDialog, setShowCustomCreateDialog] = useState<boolean>(false)
const [anchorElement, setAnchorElement] = useState<HTMLElement | null>(null)
const open = Boolean(anchorElement)
@ -87,18 +82,11 @@ export default function CreateAliasButton({
>
{t("routes.AliasesRoute.actions.createRandomAlias.label")}
</Button>
<Button
size="small"
onClick={event => setAnchorElement(event.currentTarget)}
>
<Button size="small" onClick={event => setAnchorElement(event.currentTarget)}>
<MdArrowDropDown />
</Button>
</ButtonGroup>
<Menu
anchorEl={anchorElement}
open={open}
onClose={() => setAnchorElement(null)}
>
<Menu anchorEl={anchorElement} open={open} onClose={() => setAnchorElement(null)}>
<MenuList>
<MenuItem
disabled={isLoading}
@ -111,9 +99,7 @@ export default function CreateAliasButton({
<FaPen />
</ListItemIcon>
<ListItemText
primary={t(
"routes.AliasesRoute.actions.createCustomAlias.label",
)}
primary={t("routes.AliasesRoute.actions.createCustomAlias.label")}
/>
</MenuItem>
</MenuList>
@ -127,13 +113,6 @@ export default function CreateAliasButton({
}}
onClose={() => setShowCustomCreateDialog(false)}
/>
<ErrorSnack message={errorMessage} />
<SuccessSnack
message={
isSuccess &&
t("relations.alias.mutations.success.aliasCreation")
}
/>
</>
)
}

View File

@ -24,15 +24,13 @@ import {LoadingButton} from "@mui/lab"
import {ImageProxyFormatType, ProxyUserAgentType, SimpleDetailResponse} from "~/server-types"
import {UpdatePreferencesData, updatePreferences} from "~/apis"
import {useUser} from "~/hooks"
import {useErrorSuccessSnacks, useUser} from "~/hooks"
import {parseFastAPIError} from "~/utils"
import {SuccessSnack} from "~/components"
import {
IMAGE_PROXY_FORMAT_TYPE_NAME_MAP,
IMAGE_PROXY_USER_AGENT_TYPE_NAME_MAP,
} from "~/constants/enum-mappings"
import AuthContext from "~/AuthContext/AuthContext"
import ErrorSnack from "~/components/ErrorSnack"
interface Form {
removeTrackers: boolean
@ -47,6 +45,7 @@ interface Form {
export default function AliasesPreferencesForm(): ReactElement {
const {_updateUser} = useContext(AuthContext)
const user = useUser()
const {showError, showSuccess} = useErrorSuccessSnacks()
const {t} = useTranslation()
const SCHEMA = yup.object().shape({
removeTrackers: yup.boolean().label(t("relations.alias.settings.removeTrackers.label")),
@ -69,7 +68,7 @@ export default function AliasesPreferencesForm(): ReactElement {
AxiosError,
UpdatePreferencesData
>(updatePreferences, {
onSuccess: (_, values) => {
onSuccess: (response, values) => {
const newUser = {
...user,
preferences: {
@ -78,8 +77,13 @@ export default function AliasesPreferencesForm(): ReactElement {
},
}
if (response.detail) {
showSuccess(response?.detail)
}
_updateUser(newUser)
},
onError: showError,
})
const formik = useFormik<Form>({
validationSchema: SCHEMA,
@ -108,7 +112,6 @@ export default function AliasesPreferencesForm(): ReactElement {
const isLarge = useMediaQuery(theme.breakpoints.up("md"))
return (
<>
<form onSubmit={formik.handleSubmit}>
<Grid container spacing={4} flexDirection="column" alignItems="center">
<Grid item>
@ -181,9 +184,7 @@ export default function AliasesPreferencesForm(): ReactElement {
>
{(formik.touched.createMailReport &&
formik.errors.createMailReport) ||
t(
"relations.alias.settings.createMailReports.helperText",
)}
t("relations.alias.settings.createMailReports.helperText")}
</FormHelperText>
</FormGroup>
</Grid>
@ -208,8 +209,7 @@ export default function AliasesPreferencesForm(): ReactElement {
formik.touched.proxyImages && formik.errors.proxyImages,
)}
>
{(formik.touched.proxyImages &&
formik.errors.proxyImages) ||
{(formik.touched.proxyImages && formik.errors.proxyImages) ||
t("relations.alias.settings.proxyImages.helperText")}
</FormHelperText>
</FormGroup>
@ -327,8 +327,5 @@ export default function AliasesPreferencesForm(): ReactElement {
</Grid>
</Grid>
</form>
<ErrorSnack message={formik.errors.detail} />
<SuccessSnack message={data?.detail} />
</>
)
}

View File

@ -20,7 +20,7 @@ export default function parseFastAPIError(
if (typeof error === "undefined") {
return {
detail: "There was an error",
detail: undefined,
}
}