mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-16 16:14:43 -04:00
fix(frontend): refactor api error handling
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
e93144eb40
commit
57bfca7b15
44 changed files with 387 additions and 465 deletions
|
@ -27,9 +27,6 @@ exports[`Note loading boundary shows an error 1`] = `
|
|||
</span>
|
||||
<span>
|
||||
children:
|
||||
<span>
|
||||
This is a mock for CreateNonExistingNoteHint
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
|
|
|
@ -32,7 +32,7 @@ export const NoteLoadingBoundary: React.FC<PropsWithChildren> = ({ children }) =
|
|||
}
|
||||
return (
|
||||
<CommonErrorPage titleI18nKey={`${error.message}.title`} descriptionI18nKey={`${error.message}.description`}>
|
||||
<ShowIf condition={error.message === 'api.note.notFound'}>
|
||||
<ShowIf condition={error.message === 'api.error.note.not_found'}>
|
||||
<CreateNonExistingNoteHint onNoteCreated={loadNoteFromServer} />
|
||||
</ShowIf>
|
||||
</CommonErrorPage>
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AuthError as AuthErrorType } from '../../../../api/auth/types'
|
||||
import React, { useMemo } from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export interface AuthErrorProps {
|
||||
error?: AuthErrorType
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an error message for auth fields when an error is present.
|
||||
*
|
||||
* @param error The error to render. Can be {@link undefined} when no error should be rendered.
|
||||
*/
|
||||
export const AuthError: React.FC<AuthErrorProps> = ({ error }) => {
|
||||
useTranslation()
|
||||
|
||||
const errorMessageI18nKey = useMemo(() => {
|
||||
switch (error) {
|
||||
case AuthErrorType.INVALID_CREDENTIALS:
|
||||
return 'login.auth.error.usernamePassword'
|
||||
case AuthErrorType.LOGIN_DISABLED:
|
||||
return 'login.auth.error.loginDisabled'
|
||||
case AuthErrorType.OPENID_ERROR:
|
||||
return 'login.auth.error.openIdLogin'
|
||||
default:
|
||||
return 'login.auth.error.other'
|
||||
}
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<Alert className='small' show={!!error} variant='danger'>
|
||||
<Trans i18nKey={errorMessageI18nKey} />
|
||||
</Alert>
|
||||
)
|
||||
}
|
|
@ -4,15 +4,13 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { doLdapLogin } from '../../../api/auth/ldap'
|
||||
import { AuthError as AuthErrorType } from '../../../api/auth/types'
|
||||
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
||||
import { AuthError } from './auth-error/auth-error'
|
||||
import { PasswordField } from './fields/password-field'
|
||||
import { UsernameField } from './fields/username-field'
|
||||
import { fetchAndSetUser } from './utils'
|
||||
import type { FormEvent } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { Button, Card, Form } from 'react-bootstrap'
|
||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export interface ViaLdapProps {
|
||||
|
@ -28,19 +26,13 @@ export const ViaLdap: React.FC<ViaLdapProps> = ({ providerName, identifier }) =>
|
|||
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState<AuthErrorType>()
|
||||
const [error, setError] = useState<string>()
|
||||
|
||||
const onLoginSubmit = useCallback(
|
||||
(event: FormEvent) => {
|
||||
doLdapLogin(identifier, username, password)
|
||||
.then(() => fetchAndSetUser())
|
||||
.catch((error: Error) => {
|
||||
setError(
|
||||
Object.values(AuthErrorType).includes(error.message as AuthErrorType)
|
||||
? (error.message as AuthErrorType)
|
||||
: AuthErrorType.OTHER
|
||||
)
|
||||
})
|
||||
.catch((error: Error) => setError(error.message))
|
||||
event.preventDefault()
|
||||
},
|
||||
[username, password, identifier]
|
||||
|
@ -58,7 +50,9 @@ export const ViaLdap: React.FC<ViaLdapProps> = ({ providerName, identifier }) =>
|
|||
<Form onSubmit={onLoginSubmit}>
|
||||
<UsernameField onChange={onUsernameChange} invalid={!!error} />
|
||||
<PasswordField onChange={onPasswordChange} invalid={!!error} />
|
||||
<AuthError error={error} />
|
||||
<Alert className='small' show={!!error} variant='danger'>
|
||||
<Trans i18nKey={error} />
|
||||
</Alert>
|
||||
|
||||
<Button type='submit' variant='primary'>
|
||||
<Trans i18nKey='login.signIn' />
|
||||
|
|
|
@ -4,18 +4,16 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { doLocalLogin } from '../../../api/auth/local'
|
||||
import { AuthError as AuthErrorType } from '../../../api/auth/types'
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
||||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import { AuthError } from './auth-error/auth-error'
|
||||
import { PasswordField } from './fields/password-field'
|
||||
import { UsernameField } from './fields/username-field'
|
||||
import { fetchAndSetUser } from './utils'
|
||||
import Link from 'next/link'
|
||||
import type { FormEvent } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import { Button, Card, Form } from 'react-bootstrap'
|
||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
/**
|
||||
|
@ -25,20 +23,14 @@ export const ViaLocal: React.FC = () => {
|
|||
const { t } = useTranslation()
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const [error, setError] = useState<AuthErrorType>()
|
||||
const [error, setError] = useState<string>()
|
||||
const allowRegister = useApplicationState((state) => state.config.allowRegister)
|
||||
|
||||
const onLoginSubmit = useCallback(
|
||||
(event: FormEvent) => {
|
||||
doLocalLogin(username, password)
|
||||
.then(() => fetchAndSetUser())
|
||||
.catch((error: Error) => {
|
||||
setError(
|
||||
Object.values(AuthErrorType).includes(error.message as AuthErrorType)
|
||||
? (error.message as AuthErrorType)
|
||||
: AuthErrorType.OTHER
|
||||
)
|
||||
})
|
||||
.catch((error: Error) => setError(error.message))
|
||||
event.preventDefault()
|
||||
},
|
||||
[username, password]
|
||||
|
@ -56,7 +48,9 @@ export const ViaLocal: React.FC = () => {
|
|||
<Form onSubmit={onLoginSubmit}>
|
||||
<UsernameField onChange={onUsernameChange} invalid={!!error} />
|
||||
<PasswordField onChange={onPasswordChange} invalid={!!error} />
|
||||
<AuthError error={error} />
|
||||
<Alert className='small' show={!!error} variant='danger'>
|
||||
<Trans i18nKey={error} />
|
||||
</Alert>
|
||||
|
||||
<div className='flex flex-row' dir='auto'>
|
||||
<Button type='submit' variant='primary' className='mx-2'>
|
||||
|
|
|
@ -4,22 +4,22 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { doLocalPasswordChange } from '../../../api/auth/local'
|
||||
import { ErrorToI18nKeyMapper } from '../../../api/common/error-to-i18n-key-mapper'
|
||||
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
||||
import { CurrentPasswordField } from '../../common/fields/current-password-field'
|
||||
import { NewPasswordField } from '../../common/fields/new-password-field'
|
||||
import { PasswordAgainField } from '../../common/fields/password-again-field'
|
||||
import { useUiNotifications } from '../../notifications/ui-notification-boundary'
|
||||
import type { FormEvent } from 'react'
|
||||
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
||||
import { Button, Card, Form } from 'react-bootstrap'
|
||||
import { Alert, Button, Card, Form } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useAsyncFn } from 'react-use'
|
||||
|
||||
/**
|
||||
* Profile page section for changing the password when using internal login.
|
||||
*/
|
||||
export const ProfileChangePassword: React.FC = () => {
|
||||
useTranslation()
|
||||
const { showErrorNotification, dispatchUiNotification } = useUiNotifications()
|
||||
const [oldPassword, setOldPassword] = useState('')
|
||||
const [newPassword, setNewPassword] = useState('')
|
||||
const [newPasswordAgain, setNewPasswordAgain] = useState('')
|
||||
|
@ -30,36 +30,44 @@ export const ProfileChangePassword: React.FC = () => {
|
|||
const onChangeNewPassword = useOnInputChange(setNewPassword)
|
||||
const onChangeNewPasswordAgain = useOnInputChange(setNewPasswordAgain)
|
||||
|
||||
const [{ error, loading, value: changeSucceeded }, doRequest] = useAsyncFn(async (): Promise<boolean> => {
|
||||
try {
|
||||
await doLocalPasswordChange(oldPassword, newPassword)
|
||||
return true
|
||||
} catch (error) {
|
||||
const foundI18nKey = new ErrorToI18nKeyMapper(error as Error, 'login.auth.error')
|
||||
.withHttpCode(401, 'invalidCredentials')
|
||||
.withBackendErrorName('loginDisabled', 'loginDisabled')
|
||||
.withBackendErrorName('passwordTooWeak', 'passwordTooWeak')
|
||||
.orFallbackI18nKey('other')
|
||||
return Promise.reject(foundI18nKey)
|
||||
} finally {
|
||||
if (formRef.current) {
|
||||
formRef.current.reset()
|
||||
}
|
||||
setOldPassword('')
|
||||
setNewPassword('')
|
||||
setNewPasswordAgain('')
|
||||
}
|
||||
}, [oldPassword, newPassword])
|
||||
|
||||
const onSubmitPasswordChange = useCallback(
|
||||
(event: FormEvent) => {
|
||||
event.preventDefault()
|
||||
doLocalPasswordChange(oldPassword, newPassword)
|
||||
.then(() =>
|
||||
dispatchUiNotification('profile.changePassword.successTitle', 'profile.changePassword.successText', {
|
||||
icon: 'check'
|
||||
})
|
||||
)
|
||||
.catch(showErrorNotification('profile.changePassword.failed'))
|
||||
.finally(() => {
|
||||
if (formRef.current) {
|
||||
formRef.current.reset()
|
||||
}
|
||||
setOldPassword('')
|
||||
setNewPassword('')
|
||||
setNewPasswordAgain('')
|
||||
})
|
||||
void doRequest()
|
||||
},
|
||||
[oldPassword, newPassword, showErrorNotification, dispatchUiNotification]
|
||||
[doRequest]
|
||||
)
|
||||
|
||||
const ready = useMemo(() => {
|
||||
return (
|
||||
!loading &&
|
||||
oldPassword.trim() !== '' &&
|
||||
newPassword.trim() !== '' &&
|
||||
newPasswordAgain.trim() !== '' &&
|
||||
newPassword === newPasswordAgain
|
||||
)
|
||||
}, [oldPassword, newPassword, newPasswordAgain])
|
||||
}, [loading, oldPassword, newPassword, newPasswordAgain])
|
||||
|
||||
return (
|
||||
<Card className='bg-dark mb-4'>
|
||||
|
@ -71,6 +79,12 @@ export const ProfileChangePassword: React.FC = () => {
|
|||
<CurrentPasswordField onChange={onChangeOldPassword} value={oldPassword} />
|
||||
<NewPasswordField onChange={onChangeNewPassword} value={newPassword} />
|
||||
<PasswordAgainField password={newPassword} onChange={onChangeNewPasswordAgain} value={newPasswordAgain} />
|
||||
<Alert className='small' show={!!error && !loading} variant={'danger'}>
|
||||
<Trans i18nKey={error?.message} />
|
||||
</Alert>
|
||||
<Alert className='small' show={changeSucceeded && !loading} variant={'success'}>
|
||||
<Trans i18nKey={'profile.changePassword.successText'} />
|
||||
</Alert>
|
||||
|
||||
<Button type='submit' variant='primary' disabled={!ready}>
|
||||
<Trans i18nKey='common.save' />
|
||||
|
|
34
frontend/src/components/register-page/register-error.tsx
Normal file
34
frontend/src/components/register-page/register-error.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ErrorToI18nKeyMapper } from '../../api/common/error-to-i18n-key-mapper'
|
||||
import React, { useMemo } from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
interface RegisterErrorProps {
|
||||
error?: Error
|
||||
}
|
||||
|
||||
export const RegisterError: React.FC<RegisterErrorProps> = ({ error }) => {
|
||||
useTranslation()
|
||||
|
||||
const errorI18nKey = useMemo(() => {
|
||||
if (!error) {
|
||||
return undefined
|
||||
}
|
||||
return new ErrorToI18nKeyMapper(error, 'login.register.error')
|
||||
.withHttpCode(409, 'usernameExisting')
|
||||
.withBackendErrorName('registrationDisabled', 'registrationDisabled')
|
||||
.withBackendErrorName('passwordTooWeak', 'passwordTooWeak')
|
||||
.orFallbackI18nKey('other')
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<Alert className='small' show={!!errorI18nKey} variant='danger'>
|
||||
<Trans i18nKey={errorI18nKey} />
|
||||
</Alert>
|
||||
)
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { RegisterError as RegisterErrorType } from '../../../api/auth/types'
|
||||
import React, { useMemo } from 'react'
|
||||
import { Alert } from 'react-bootstrap'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export interface RegisterErrorProps {
|
||||
error?: RegisterErrorType
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders an error message for registration fields when an error is present.
|
||||
*
|
||||
* @param error The error to render. Can be {@link undefined} when no error should be rendered.
|
||||
*/
|
||||
export const RegisterError: React.FC<RegisterErrorProps> = ({ error }) => {
|
||||
useTranslation()
|
||||
|
||||
const errorMessageI18nKey = useMemo(() => {
|
||||
switch (error) {
|
||||
case RegisterErrorType.PASSWORD_TOO_WEAK:
|
||||
return 'login.register.error.passwordTooWeak'
|
||||
case RegisterErrorType.REGISTRATION_DISABLED:
|
||||
return 'login.register.error.registrationDisabled'
|
||||
case RegisterErrorType.USERNAME_EXISTING:
|
||||
return 'login.register.error.usernameExisting'
|
||||
default:
|
||||
return 'login.register.error.other'
|
||||
}
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<Alert className='small' show={!!error} variant='danger'>
|
||||
<Trans i18nKey={errorMessageI18nKey} />
|
||||
</Alert>
|
||||
)
|
||||
}
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { TranslatedExternalLink } from '../../common/links/translated-external-link'
|
||||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||
import { TranslatedExternalLink } from '../common/links/translated-external-link'
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
import React from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue