feat: merge login, register and intro page

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-09-10 08:54:03 +02:00
parent 76ef2511e7
commit 56643ffd85
39 changed files with 533 additions and 374 deletions

View file

@ -6,10 +6,10 @@
import { refreshHistoryState } from '../../../redux/history/methods'
import { Logger } from '../../../utils/logger'
import { isDevMode, isTestMode } from '../../../utils/test-modes'
import { fetchAndSetUser } from '../../login-page/auth/utils'
import { loadDarkMode } from './load-dark-mode'
import { setUpI18n } from './setupI18n'
import { loadFromLocalStorage } from '../../../redux/editor/methods'
import { fetchAndSetUser } from '../../login-page/utils/fetch-and-set-user'
const logger = new Logger('Application Loader')

View file

@ -7,13 +7,13 @@ import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import { cypressId } from '../../../utils/cypress-attribute'
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
import { ShowIf } from '../../common/show-if/show-if'
import { filterOneClickProviders } from '../../login-page/auth/utils'
import { getOneClickProviderMetadata } from '../../login-page/auth/utils/get-one-click-provider-metadata'
import Link from 'next/link'
import React, { useMemo } from 'react'
import { Button } from 'react-bootstrap'
import type { ButtonProps } from 'react-bootstrap/Button'
import { Trans } from 'react-i18next'
import { filterOneClickProviders } from '../../login-page/utils/filter-one-click-providers'
import { getOneClickProviderMetadata } from '../../login-page/one-click/get-one-click-provider-metadata'
export type SignInButtonProps = Omit<ButtonProps, 'href'>
@ -28,7 +28,7 @@ export const SignInButton: React.FC<SignInButtonProps> = ({ variant, ...props })
const authProviders = useFrontendConfig().authProviders
const loginLink = useMemo(() => {
const oneClickProviders = authProviders.filter(filterOneClickProviders)
const oneClickProviders = filterOneClickProviders(authProviders)
if (authProviders.length === 1 && oneClickProviders.length === 1) {
const metadata = getOneClickProviderMetadata(oneClickProviders[0])
return metadata.url

View file

@ -21,7 +21,7 @@ export const HelpDropdown: React.FC = () => {
useTranslation()
return (
<Dropdown>
<Dropdown drop={'start'}>
<Dropdown.Toggle size={'sm'} className={'h-100'}>
<UiIcon icon={IconQuestion} />
</Dropdown.Toggle>

View file

@ -15,13 +15,17 @@ import type { PropsWithChildren } from 'react'
import React from 'react'
import { Nav, Navbar } from 'react-bootstrap'
import { HistoryButton } from './app-bar-elements/help-dropdown/history-button'
import { cypressId } from '../../../utils/cypress-attribute'
/**
* Renders the base app bar with branding, help, settings user elements.
*/
export const BaseAppBar: React.FC<PropsWithChildren> = ({ children }) => {
return (
<Navbar expand={true} className={`px-2 py-1 align-items-center border-bottom ${styles.navbar}`}>
<Navbar
expand={true}
className={`px-2 py-1 align-items-center border-bottom ${styles.navbar}`}
{...cypressId('base-app-bar')}>
<Nav className={`align-items-center justify-content-start gap-2 flex-grow-1 ${styles.side}`}>
<BrandingElement />
</Nav>

View file

@ -1,11 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { ChangeEvent } from 'react'
export interface AuthFieldProps {
onChange: (event: ChangeEvent<HTMLInputElement>) => void
invalid: boolean
}

View file

@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react'
import { Card } from 'react-bootstrap'
import { NewNoteButton } from '../../common/new-note-button/new-note-button'
import { HistoryButton } from '../../layout/app-bar/app-bar-elements/help-dropdown/history-button'
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
import { Trans, useTranslation } from 'react-i18next'
/**
* Renders the card with the options for not logged-in users.
*/
export const GuestCard: React.FC = () => {
const allowAnonymous = useFrontendConfig().allowAnonymous
useTranslation()
if (!allowAnonymous) {
return null
}
return (
<Card>
<Card.Body>
<Card.Title>
<Trans i18nKey={'login.guest.title'}></Trans>
</Card.Title>
<div className={'d-flex flex-row gap-2'}>
<NewNoteButton />
<HistoryButton />
</div>
</Card.Body>
</Card>
)
}

View file

@ -1,5 +1,5 @@
/*
* 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
*/
@ -7,12 +7,12 @@ import { doLdapLogin } from '../../../api/auth/ldap'
import { useLowercaseOnInputChange } from '../../../hooks/common/use-lowercase-on-input-change'
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
import { UsernameField } from '../../common/fields/username-field'
import { PasswordField } from './fields/password-field'
import { fetchAndSetUser } from './utils'
import type { FormEvent } from 'react'
import React, { useCallback, useState } from 'react'
import { Alert, Button, Card, Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { fetchAndSetUser } from '../utils/fetch-and-set-user'
import { PasswordField } from '../password-field'
export interface ViaLdapProps {
providerName: string
@ -22,7 +22,7 @@ export interface ViaLdapProps {
/**
* Renders the LDAP login box with username and password field.
*/
export const ViaLdap: React.FC<ViaLdapProps> = ({ providerName, identifier }) => {
export const LdapLoginCard: React.FC<ViaLdapProps> = ({ providerName, identifier }) => {
useTranslation()
const [username, setUsername] = useState('')
@ -43,12 +43,12 @@ export const ViaLdap: React.FC<ViaLdapProps> = ({ providerName, identifier }) =>
const onPasswordChange = useOnInputChange(setPassword)
return (
<Card className='bg-dark mb-4'>
<Card>
<Card.Body>
<Card.Title>
<Trans i18nKey='login.signInVia' values={{ service: providerName }} />
</Card.Title>
<Form onSubmit={onLoginSubmit}>
<Form onSubmit={onLoginSubmit} className={'d-flex gap-3 flex-column'}>
<UsernameField onChange={onUsernameChange} isValid={!!error} value={username} />
<PasswordField onChange={onPasswordChange} invalid={!!error} />
<Alert className='small' show={!!error} variant='danger'>

View file

@ -0,0 +1,35 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { Fragment, useMemo } from 'react'
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
import type { AuthProviderWithCustomName } from '../../../api/config/types'
import { AuthProviderType } from '../../../api/config/types'
import { LdapLoginCard } from './ldap-login-card'
/**
* Renders a ldap login card for every ldap auth provider.
*/
export const LdapLoginCards: React.FC = () => {
const authProviders = useFrontendConfig().authProviders
const ldapProviders = useMemo(() => {
return authProviders
.filter((provider) => provider.type === AuthProviderType.LDAP)
.map((provider) => {
const ldapProvider = provider as AuthProviderWithCustomName
return (
<LdapLoginCard
providerName={ldapProvider.providerName}
identifier={ldapProvider.identifier}
key={ldapProvider.identifier}
/>
)
})
}, [authProviders])
return ldapProviders.length === 0 ? null : <Fragment>{ldapProviders}</Fragment>
}

View file

@ -8,28 +8,25 @@ import { ErrorToI18nKeyMapper } from '../../../api/common/error-to-i18n-key-mapp
import { useLowercaseOnInputChange } from '../../../hooks/common/use-lowercase-on-input-change'
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
import { UsernameField } from '../../common/fields/username-field'
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
import { ShowIf } from '../../common/show-if/show-if'
import { PasswordField } from './fields/password-field'
import { fetchAndSetUser } from './utils'
import Link from 'next/link'
import type { FormEvent } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import { Alert, Button, Card, Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { fetchAndSetUser } from '../utils/fetch-and-set-user'
import { PasswordField } from '../password-field'
/**
* Renders the local login box with username and password field and the optional button for registering a new user.
*/
export const ViaLocal: React.FC = () => {
export const LocalLoginCardBody: React.FC = () => {
const { t } = useTranslation()
const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState<string>()
const allowRegister = useFrontendConfig().allowRegister
const onLoginSubmit = useCallback(
(event: FormEvent) => {
event.preventDefault()
doLocalLogin(username, password)
.then(() => fetchAndSetUser())
.catch((error: Error) => {
@ -40,7 +37,6 @@ export const ViaLocal: React.FC = () => {
.orFallbackI18nKey('other')
setError(errorI18nKey)
})
event.preventDefault()
},
[username, password]
)
@ -51,30 +47,20 @@ export const ViaLocal: React.FC = () => {
const translationOptions = useMemo(() => ({ service: t('login.auth.username') }), [t])
return (
<Card className='mb-4'>
<Card.Body>
<Card.Title>
<Trans i18nKey='login.signInVia' values={translationOptions} />
</Card.Title>
<Form onSubmit={onLoginSubmit} className={'d-flex gap-3 flex-column'}>
<UsernameField onChange={onUsernameChange} isInvalid={!!error} value={username} />
<PasswordField onChange={onPasswordChange} invalid={!!error} />
<Alert className='small' show={!!error} variant='danger'>
<Trans i18nKey={error} />
</Alert>
<Button type='submit' variant='primary'>
<Trans i18nKey='login.signIn' />
</Button>
<ShowIf condition={allowRegister}>
<Trans i18nKey={'login.register.question'} />
<Link href={'/register'} passHref={true}>
<Button type='button' variant='secondary' className={'d-block w-100'}>
<Trans i18nKey='login.register.title' />
</Button>
</Link>
</ShowIf>
</Form>
</Card.Body>
</Card>
<Card.Body>
<Card.Title>
<Trans i18nKey='login.signInVia' values={translationOptions} />
</Card.Title>
<Form onSubmit={onLoginSubmit} className={'d-flex gap-3 flex-column'}>
<UsernameField onChange={onUsernameChange} isInvalid={!!error} value={username} />
<PasswordField onChange={onPasswordChange} invalid={!!error} />
<Alert className='small' show={!!error} variant='danger'>
<Trans i18nKey={error} />
</Alert>
<Button type='submit' variant='primary'>
<Trans i18nKey='login.signIn' />
</Button>
</Form>
</Card.Body>
)
}

View file

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useMemo } from 'react'
import { Card } from 'react-bootstrap'
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
import { AuthProviderType } from '../../../api/config/types'
import { LocalLoginCardBody } from './local-login-card-body'
import { LocalRegisterCardBody } from './register/local-register-card-body'
import { ShowIf } from '../../common/show-if/show-if'
/**
* Shows the card that processes local logins and registers.
*/
export const LocalLoginCard: React.FC = () => {
const frontendConfig = useFrontendConfig()
const localLoginEnabled = useMemo(() => {
return frontendConfig.authProviders.some((provider) => provider.type === AuthProviderType.LOCAL)
}, [frontendConfig])
if (!localLoginEnabled) {
return null
}
return (
<Card>
<LocalLoginCardBody />
<ShowIf condition={frontendConfig.allowRegister}>
<hr className={'m-0'} />
<LocalRegisterCardBody />
</ShowIf>
</Card>
)
}

View file

@ -0,0 +1,26 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react'
import { Button } from 'react-bootstrap'
import { Trans } from 'react-i18next'
interface LocalRegisterButtonProps {
onClick: () => void
}
/**
* Shows a button that triggers the register process.
*
* @param onClick The callback that is executed when the button is clicked
*/
export const LocalRegisterButton: React.FC<LocalRegisterButtonProps> = ({ onClick }) => {
return (
<Button type='button' variant='secondary' onClick={onClick}>
<Trans i18nKey='login.register.title' />
</Button>
)
}

View file

@ -0,0 +1,39 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useCallback, useMemo, useState } from 'react'
import { Card } from 'react-bootstrap'
import { Trans } from 'react-i18next'
import { LocalRegisterForm } from './local-register-form'
import { LocalRegisterButton } from './local-register-button'
/**
* Shows a bootstrap card body for the register process that switches from a button to the form.
*/
export const LocalRegisterCardBody: React.FC = () => {
const [showForm, setShowForm] = useState(false)
const doShowForm = useCallback(() => {
setShowForm(true)
}, [])
const content = useMemo(() => {
if (showForm) {
return <LocalRegisterForm />
} else {
return <LocalRegisterButton onClick={doShowForm} />
}
}, [doShowForm, showForm])
return (
<Card.Body>
<Card.Title>
<Trans i18nKey={'login.register.question'} />
</Card.Title>
{content}
</Card.Body>
)
}

View file

@ -0,0 +1,95 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
'use client'
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { NextPage } from 'next'
import { useRouter } from 'next/navigation'
import type { FormEvent } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import { Button, Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { useUiNotifications } from '../../../notifications/ui-notification-boundary'
import type { ApiError } from '../../../../api/common/api-error'
import { doLocalRegister } from '../../../../api/auth/local'
import { useLowercaseOnInputChange } from '../../../../hooks/common/use-lowercase-on-input-change'
import { useOnInputChange } from '../../../../hooks/common/use-on-input-change'
import { UsernameLabelField } from '../../../common/fields/username-label-field'
import { DisplayNameField } from '../../../common/fields/display-name-field'
import { NewPasswordField } from '../../../common/fields/new-password-field'
import { PasswordAgainField } from '../../../common/fields/password-again-field'
import { RegisterInfos } from '../../../register-page/register-infos'
import { RegisterError } from '../../../register-page/register-error'
import { fetchAndSetUser } from '../../utils/fetch-and-set-user'
/**
* Renders the registration process with fields for username, display name, password, password retype and information about terms and conditions.
*/
export const LocalRegisterForm: NextPage = () => {
useTranslation()
const router = useRouter()
const [username, setUsername] = useState('')
const [displayName, setDisplayName] = useState('')
const [password, setPassword] = useState('')
const [passwordAgain, setPasswordAgain] = useState('')
const [error, setError] = useState<ApiError>()
const { dispatchUiNotification } = useUiNotifications()
const doRegisterSubmit = useCallback(
(event: FormEvent) => {
doLocalRegister(username, displayName, password)
.then(() => fetchAndSetUser())
.then(() => dispatchUiNotification('login.register.success.title', 'login.register.success.message', {}))
.then(() => router.push('/'))
.catch((error: ApiError) => setError(error))
event.preventDefault()
},
[username, displayName, password, dispatchUiNotification, router]
)
const ready = useMemo(() => {
return username.trim() !== '' && displayName.trim() !== '' && password.trim() !== '' && password === passwordAgain
}, [username, password, displayName, passwordAgain])
const isWeakPassword = useMemo(() => {
return error?.backendErrorName === 'PasswordTooWeakError'
}, [error])
const isValidUsername = useMemo(() => Boolean(username.trim()), [username])
const onUsernameChange = useLowercaseOnInputChange(setUsername)
const onDisplayNameChange = useOnInputChange(setDisplayName)
const onPasswordChange = useOnInputChange(setPassword)
const onPasswordAgainChange = useOnInputChange(setPasswordAgain)
return (
<Form onSubmit={doRegisterSubmit} className={'d-flex flex-column gap-3'}>
<UsernameLabelField onChange={onUsernameChange} value={username} isValid={isValidUsername} />
<DisplayNameField onChange={onDisplayNameChange} value={displayName} />
<NewPasswordField onChange={onPasswordChange} value={password} hasError={isWeakPassword} />
<PasswordAgainField
password={password}
onChange={onPasswordAgainChange}
value={passwordAgain}
hasError={isWeakPassword}
/>
<RegisterInfos />
<Button variant='primary' type='submit' disabled={!ready}>
<Trans i18nKey='login.register.title' />
</Button>
<RegisterError error={error} />
</Form>
)
}

View file

@ -1,13 +1,9 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { AuthProvider } from '../../../../api/config/types'
import { AuthProviderType } from '../../../../api/config/types'
import { Logger } from '../../../../utils/logger'
import { IconGitlab } from '../../../common/icons/additional/icon-gitlab'
import styles from '../via-one-click.module.scss'
import styles from './via-one-click.module.scss'
import type { Icon } from 'react-bootstrap-icons'
import {
Dropbox as IconDropbox,
@ -19,6 +15,10 @@ import {
PersonRolodex as IconPersonRolodex,
Twitter as IconTwitter
} from 'react-bootstrap-icons'
import { Logger } from '../../../utils/logger'
import type { AuthProvider } from '../../../api/config/types'
import { AuthProviderType } from '../../../api/config/types'
import { IconGitlab } from '../../common/icons/additional/icon-gitlab'
export interface OneClickMetadata {
name: string

View file

@ -1,12 +1,12 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { AuthProvider, AuthProviderWithCustomName } from '../../../api/config/types'
import { IconButton } from '../../common/icon-button/icon-button'
import { getOneClickProviderMetadata } from './utils/get-one-click-provider-metadata'
import React, { useMemo } from 'react'
import { getOneClickProviderMetadata } from './get-one-click-provider-metadata'
export interface ViaOneClickProps {
provider: AuthProvider
@ -17,7 +17,7 @@ export interface ViaOneClickProps {
*
* @param provider The one-click login provider. In case of ones that can be defined multiple times, an identifier and a label is required.
*/
export const ViaOneClick: React.FC<ViaOneClickProps> = ({ provider }) => {
export const OneClickLoginButton: React.FC<ViaOneClickProps> = ({ provider }) => {
const { className, icon, url, name } = useMemo(() => getOneClickProviderMetadata(provider), [provider])
const text = (provider as AuthProviderWithCustomName).providerName || name

View file

@ -0,0 +1,38 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useMemo } from 'react'
import { Card } from 'react-bootstrap'
import { Trans } from 'react-i18next'
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
import { OneClickLoginButton } from './one-click-login-button'
import { filterOneClickProviders } from '../utils/filter-one-click-providers'
/**
* Shows a card that contains buttons to all one click auth providers.
*/
export const OneClickLoginCard: React.FC = () => {
const authProviders = useFrontendConfig().authProviders
const oneClickProviders = useMemo(() => {
return filterOneClickProviders(authProviders).map((provider, index) => (
<div className={'p-2 d-flex flex-column social-button-container'} key={index}>
<OneClickLoginButton provider={provider} />
</div>
))
}, [authProviders])
return oneClickProviders.length === 0 ? null : (
<Card>
<Card.Body>
<Card.Title>
<Trans i18nKey='login.signInVia' values={{ service: '' }} />
</Card.Title>
{oneClickProviders}
</Card.Body>
</Card>
)
}

View file

@ -1,12 +1,17 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../../hooks/common/use-translated-text'
import type { AuthFieldProps } from './fields'
import type { ChangeEvent } from 'react'
import React from 'react'
import { Form } from 'react-bootstrap'
import { useTranslatedText } from '../../hooks/common/use-translated-text'
export interface AuthFieldProps {
onChange: (event: ChangeEvent<HTMLInputElement>) => void
invalid: boolean
}
/**
* Renders an input field for the password of a user.

View file

@ -0,0 +1,24 @@
/*
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React from 'react'
import { Redirect } from '../common/redirect'
import { useSingleStringUrlParameter } from '../../hooks/common/use-single-string-url-parameter'
const defaultFallback = '/history'
/**
* Redirects the browser to the relative URL that is provided via "redirectBackTo" URL parameter.
* If no parameter has been provided or if the URL is not relative then "/history" will be used.
*/
export const RedirectToParamOrHistory: React.FC = () => {
const redirectBackUrl = useSingleStringUrlParameter('redirectBackTo', defaultFallback)
const cleanedUrl =
redirectBackUrl.startsWith('/') && !redirectBackUrl.startsWith('//') ? redirectBackUrl : defaultFallback
return <Redirect to={cleanedUrl} />
}

View file

@ -3,8 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { AuthProvider } from '../../../api/config/types'
import { authProviderTypeOneClick } from '../../../api/config/types'
import { getMe } from '../../../api/me'
import { setUser } from '../../../redux/user/methods'
@ -21,13 +20,3 @@ export const fetchAndSetUser: () => Promise<void> = async () => {
email: me.email
})
}
/**
* Filter to apply to a list of auth providers to get only one-click providers.
*
* @param provider The provider to test whether it is a one-click provider or not.
* @return {@link true} when the provider is a one-click one, {@link false} otherwise.
*/
export const filterOneClickProviders = (provider: AuthProvider): boolean => {
return authProviderTypeOneClick.includes(provider.type)
}

View file

@ -0,0 +1,17 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { AuthProvider } from '../../../api/config/types'
import { authProviderTypeOneClick } from '../../../api/config/types'
/**
* Filters the given auth providers to one-click providers only.
* @param authProviders The auth providers to filter
* @return only one click auth providers
*/
export const filterOneClickProviders = (authProviders: AuthProvider[]) => {
return authProviders.filter((provider: AuthProvider): boolean => authProviderTypeOneClick.includes(provider.type))
}

View file

@ -7,12 +7,12 @@ import { updateDisplayName } from '../../../api/me'
import { useApplicationState } from '../../../hooks/common/use-application-state'
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
import { DisplayNameField } from '../../common/fields/display-name-field'
import { fetchAndSetUser } from '../../login-page/auth/utils'
import { useUiNotifications } from '../../notifications/ui-notification-boundary'
import type { FormEvent } from 'react'
import React, { useCallback, useMemo, useState } from 'react'
import { Button, Card, Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { fetchAndSetUser } from '../../login-page/utils/fetch-and-set-user'
/**
* Profile page section for changing the current display name.