mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-07 20:31:06 -05:00
Merge pull request #7808 from overleaf/ii-settings-fixes-1
[SettingsPage] UI Fixes 1 GitOrigin-RevId: 0e0f605191218af4db70a801ff1e50b47f6e0b01
This commit is contained in:
parent
85f731110c
commit
1def576973
14 changed files with 221 additions and 135 deletions
|
@ -18,6 +18,7 @@ function EmailsSectionContent() {
|
||||||
state: { data: userEmailsData },
|
state: { data: userEmailsData },
|
||||||
isInitializing,
|
isInitializing,
|
||||||
isInitializingError,
|
isInitializingError,
|
||||||
|
isInitializingSuccess,
|
||||||
} = useUserEmailsContext()
|
} = useUserEmailsContext()
|
||||||
const userEmails = Object.values(userEmailsData.byId)
|
const userEmails = Object.values(userEmailsData.byId)
|
||||||
|
|
||||||
|
@ -32,11 +33,13 @@ function EmailsSectionContent() {
|
||||||
<a href="/learn/how-to/Keeping_your_account_secure" />
|
<a href="/learn/how-to/Keeping_your_account_secure" />
|
||||||
</Trans>
|
</Trans>
|
||||||
</p>
|
</p>
|
||||||
<div className="table">
|
<>
|
||||||
<EmailsHeader />
|
<EmailsHeader />
|
||||||
{isInitializing ? (
|
{isInitializing ? (
|
||||||
<div className="text-center">
|
<div className="affiliations-table-row--highlighted">
|
||||||
<Icon type="refresh" fw spin /> {t('loading')}...
|
<div className="affiliations-table-cell text-center">
|
||||||
|
<Icon type="refresh" fw spin /> {t('loading')}...
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -48,14 +51,14 @@ function EmailsSectionContent() {
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<AddEmail />
|
{isInitializingSuccess && <AddEmail />}
|
||||||
{isInitializingError && (
|
{isInitializingError && (
|
||||||
<Alert bsStyle="danger" className="text-center">
|
<Alert bsStyle="danger" className="text-center">
|
||||||
<Icon type="exclamation-triangle" fw />{' '}
|
<Icon type="exclamation-triangle" fw />{' '}
|
||||||
{t('error_performing_request')}
|
{t('error_performing_request')}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -73,7 +76,6 @@ function EmailsSection() {
|
||||||
<UserEmailsProvider>
|
<UserEmailsProvider>
|
||||||
<EmailsSectionContent />
|
<EmailsSectionContent />
|
||||||
</UserEmailsProvider>
|
</UserEmailsProvider>
|
||||||
<hr />
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@ import { useEffect } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import MakePrimary from './actions/make-primary'
|
import MakePrimary from './actions/make-primary'
|
||||||
import Remove from './actions/remove'
|
import Remove from './actions/remove'
|
||||||
import Icon from '../../../../shared/components/icon'
|
|
||||||
import useAsync from '../../../../shared/hooks/use-async'
|
import useAsync from '../../../../shared/hooks/use-async'
|
||||||
import { useUserEmailsContext } from '../../context/user-email-context'
|
import { useUserEmailsContext } from '../../context/user-email-context'
|
||||||
import { UserEmailData } from '../../../../../../types/user-email'
|
import { UserEmailData } from '../../../../../../types/user-email'
|
||||||
|
@ -51,8 +50,7 @@ function Actions({ userEmailData }: ActionsProps) {
|
||||||
/>
|
/>
|
||||||
{(makePrimaryAsync.isError || deleteEmailAsync.isError) && (
|
{(makePrimaryAsync.isError || deleteEmailAsync.isError) && (
|
||||||
<div className="text-danger small">
|
<div className="text-danger small">
|
||||||
<Icon type="exclamation-triangle" fw />{' '}
|
{t('generic_something_went_wrong')}
|
||||||
{t('error_performing_request')}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useState,
|
useState,
|
||||||
|
forwardRef,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import { getJSON } from '../../../../infrastructure/fetch-json'
|
import { getJSON } from '../../../../infrastructure/fetch-json'
|
||||||
import useAbortController from '../../../../shared/hooks/use-abort-controller'
|
import useAbortController from '../../../../shared/hooks/use-abort-controller'
|
||||||
|
@ -38,13 +39,14 @@ export function clearDomainCache() {
|
||||||
|
|
||||||
type AddEmailInputProps = {
|
type AddEmailInputProps = {
|
||||||
onChange: (value: string, institution?: InstitutionInfo) => void
|
onChange: (value: string, institution?: InstitutionInfo) => void
|
||||||
|
inputRef?: React.ForwardedRef<HTMLInputElement>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AddEmailInput({ onChange }: AddEmailInputProps) {
|
function AddEmailInputBase({ onChange, inputRef }: AddEmailInputProps) {
|
||||||
const { signal } = useAbortController()
|
const { signal } = useAbortController()
|
||||||
|
|
||||||
const [suggestion, setSuggestion] = useState<string>(null)
|
const [suggestion, setSuggestion] = useState<string | null>(null)
|
||||||
const [inputValue, setInputValue] = useState<string>(null)
|
const [inputValue, setInputValue] = useState<string | null>(null)
|
||||||
const [matchedInstitution, setMatchedInstitution] =
|
const [matchedInstitution, setMatchedInstitution] =
|
||||||
useState<InstitutionInfo>(null)
|
useState<InstitutionInfo>(null)
|
||||||
|
|
||||||
|
@ -52,7 +54,10 @@ export function AddEmailInput({ onChange }: AddEmailInputProps) {
|
||||||
if (inputValue == null) {
|
if (inputValue == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (matchedInstitution && suggestion === inputValue) {
|
if (
|
||||||
|
matchedInstitution &&
|
||||||
|
inputValue.endsWith(matchedInstitution.hostname)
|
||||||
|
) {
|
||||||
onChange(inputValue, matchedInstitution)
|
onChange(inputValue, matchedInstitution)
|
||||||
} else {
|
} else {
|
||||||
onChange(inputValue)
|
onChange(inputValue)
|
||||||
|
@ -103,12 +108,23 @@ export function AddEmailInput({ onChange }: AddEmailInputProps) {
|
||||||
|
|
||||||
const handleKeyDownEvent = useCallback(
|
const handleKeyDownEvent = useCallback(
|
||||||
(event: KeyboardEvent) => {
|
(event: KeyboardEvent) => {
|
||||||
if (event.key === 'Tab' || event.key === 'Enter') {
|
const setInputValueAndResetSuggestion = () => {
|
||||||
|
setInputValue(suggestion)
|
||||||
|
setSuggestion(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === 'Enter') {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
if (suggestion) {
|
if (suggestion) {
|
||||||
setInputValue(suggestion)
|
setInputValueAndResetSuggestion()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.key === 'Tab' && suggestion) {
|
||||||
|
event.preventDefault()
|
||||||
|
setInputValueAndResetSuggestion()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[suggestion]
|
[suggestion]
|
||||||
)
|
)
|
||||||
|
@ -129,7 +145,17 @@ export function AddEmailInput({ onChange }: AddEmailInputProps) {
|
||||||
onKeyDown={handleKeyDownEvent}
|
onKeyDown={handleKeyDownEvent}
|
||||||
value={inputValue || ''}
|
value={inputValue || ''}
|
||||||
placeholder="e.g. johndoe@mit.edu"
|
placeholder="e.g. johndoe@mit.edu"
|
||||||
|
ref={inputRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AddEmailInput = forwardRef<
|
||||||
|
HTMLInputElement,
|
||||||
|
Omit<AddEmailInputProps, 'inputRef'>
|
||||||
|
>((props, ref) => <AddEmailInputBase {...props} inputRef={ref} />)
|
||||||
|
|
||||||
|
AddEmailInput.displayName = 'AddEmailInput'
|
||||||
|
|
||||||
|
export { AddEmailInput }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Button, Row, Col } from 'react-bootstrap'
|
import { Button, Row, Col, Alert } from 'react-bootstrap'
|
||||||
import Cell from './cell'
|
import Cell from './cell'
|
||||||
import Icon from '../../../../shared/components/icon'
|
import Icon from '../../../../shared/components/icon'
|
||||||
import DownshiftInput from './downshift-input'
|
import DownshiftInput from './downshift-input'
|
||||||
|
@ -39,6 +39,8 @@ function AddEmail() {
|
||||||
const [isFormVisible, setIsFormVisible] = useState(
|
const [isFormVisible, setIsFormVisible] = useState(
|
||||||
() => window.location.hash === '#add-email'
|
() => window.location.hash === '#add-email'
|
||||||
)
|
)
|
||||||
|
const emailRef = useRef<HTMLInputElement | null>(null)
|
||||||
|
const countryRef = useRef<HTMLInputElement | null>(null)
|
||||||
const [newEmail, setNewEmail] = useState('')
|
const [newEmail, setNewEmail] = useState('')
|
||||||
const [newEmailMatchedInstitution, setNewEmailMatchedInstitution] =
|
const [newEmailMatchedInstitution, setNewEmailMatchedInstitution] =
|
||||||
useState<InstitutionInfo | null>(null)
|
useState<InstitutionInfo | null>(null)
|
||||||
|
@ -53,7 +55,7 @@ function AddEmail() {
|
||||||
const [isInstitutionFieldsVisible, setIsInstitutionFieldsVisible] =
|
const [isInstitutionFieldsVisible, setIsInstitutionFieldsVisible] =
|
||||||
useState(false)
|
useState(false)
|
||||||
const [isUniversityDirty, setIsUniversityDirty] = useState(false)
|
const [isUniversityDirty, setIsUniversityDirty] = useState(false)
|
||||||
const { isLoading, isError, runAsync } = useAsync()
|
const { isLoading, isError, error, runAsync } = useAsync()
|
||||||
const { runAsync: institutionRunAsync } = useAsync()
|
const { runAsync: institutionRunAsync } = useAsync()
|
||||||
const {
|
const {
|
||||||
state,
|
state,
|
||||||
|
@ -65,6 +67,18 @@ function AddEmail() {
|
||||||
setUserEmailsContextLoading(isLoading)
|
setUserEmailsContextLoading(isLoading)
|
||||||
}, [setUserEmailsContextLoading, isLoading])
|
}, [setUserEmailsContextLoading, isLoading])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isFormVisible && emailRef.current) {
|
||||||
|
emailRef.current?.focus()
|
||||||
|
}
|
||||||
|
}, [emailRef, isFormVisible])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isInstitutionFieldsVisible && countryRef.current) {
|
||||||
|
countryRef.current?.focus()
|
||||||
|
}
|
||||||
|
}, [countryRef, isInstitutionFieldsVisible])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (university) {
|
if (university) {
|
||||||
setIsUniversityDirty(true)
|
setIsUniversityDirty(true)
|
||||||
|
@ -194,7 +208,7 @@ function AddEmail() {
|
||||||
<label htmlFor="affiliations-email" className="sr-only">
|
<label htmlFor="affiliations-email" className="sr-only">
|
||||||
{t('email')}
|
{t('email')}
|
||||||
</label>
|
</label>
|
||||||
<AddEmailInput onChange={handleEmailChange} />
|
<AddEmailInput onChange={handleEmailChange} ref={emailRef} />
|
||||||
</Cell>
|
</Cell>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
|
@ -211,7 +225,7 @@ function AddEmail() {
|
||||||
|
|
||||||
{!ssoAvailable && (
|
{!ssoAvailable && (
|
||||||
<>
|
<>
|
||||||
<Col md={4}>
|
<Col md={5}>
|
||||||
<Cell>
|
<Cell>
|
||||||
{isInstitutionFieldsVisible ? (
|
{isInstitutionFieldsVisible ? (
|
||||||
<>
|
<>
|
||||||
|
@ -219,6 +233,7 @@ function AddEmail() {
|
||||||
<CountryInput
|
<CountryInput
|
||||||
id="new-email-country-input"
|
id="new-email-country-input"
|
||||||
setValue={setCountryCode}
|
setValue={setCountryCode}
|
||||||
|
ref={countryRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="form-group mb-2">
|
<div className="form-group mb-2">
|
||||||
|
@ -269,7 +284,7 @@ function AddEmail() {
|
||||||
</Cell>
|
</Cell>
|
||||||
</Col>
|
</Col>
|
||||||
|
|
||||||
<Col md={4}>
|
<Col md={3}>
|
||||||
<Cell className="text-md-right">
|
<Cell className="text-md-right">
|
||||||
<Button
|
<Button
|
||||||
bsSize="small"
|
bsSize="small"
|
||||||
|
@ -281,12 +296,6 @@ function AddEmail() {
|
||||||
>
|
>
|
||||||
{t('add_new_email')}
|
{t('add_new_email')}
|
||||||
</Button>
|
</Button>
|
||||||
{isError && (
|
|
||||||
<div className="text-danger small">
|
|
||||||
<Icon type="exclamation-triangle" fw />{' '}
|
|
||||||
{t('error_performing_request')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</Cell>
|
</Cell>
|
||||||
</Col>
|
</Col>
|
||||||
</>
|
</>
|
||||||
|
@ -294,6 +303,11 @@ function AddEmail() {
|
||||||
</form>
|
</form>
|
||||||
)}
|
)}
|
||||||
</Row>
|
</Row>
|
||||||
|
{isError && (
|
||||||
|
<Alert bsStyle="danger" className="text-center">
|
||||||
|
<Icon type="exclamation-triangle" fw /> {error.getUserFacingMessage()}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
|
import { useState, forwardRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useState } from 'react'
|
|
||||||
import { useCombobox } from 'downshift'
|
import { useCombobox } from 'downshift'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
import { defaults as countries } from '../../countries-list'
|
import { defaults as countries } from '../../countries-list'
|
||||||
|
@ -7,11 +7,12 @@ import { CountryCode } from '../../../../../../types/country'
|
||||||
|
|
||||||
type CountryInputProps = {
|
type CountryInputProps = {
|
||||||
setValue: React.Dispatch<React.SetStateAction<CountryCode | null>>
|
setValue: React.Dispatch<React.SetStateAction<CountryCode | null>>
|
||||||
|
inputRef?: React.ForwardedRef<HTMLInputElement>
|
||||||
} & React.InputHTMLAttributes<HTMLInputElement>
|
} & React.InputHTMLAttributes<HTMLInputElement>
|
||||||
|
|
||||||
const itemToString = (item: typeof countries[number] | null) => item?.name ?? ''
|
const itemToString = (item: typeof countries[number] | null) => item?.name ?? ''
|
||||||
|
|
||||||
function CountryInput({ setValue }: CountryInputProps) {
|
function Downshift({ setValue, inputRef }: CountryInputProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [inputItems, setInputItems] = useState(() => countries)
|
const [inputItems, setInputItems] = useState(() => countries)
|
||||||
const [inputValue, setInputValue] = useState('')
|
const [inputValue, setInputValue] = useState('')
|
||||||
|
@ -23,6 +24,7 @@ function CountryInput({ setValue }: CountryInputProps) {
|
||||||
getInputProps,
|
getInputProps,
|
||||||
getComboboxProps,
|
getComboboxProps,
|
||||||
getItemProps,
|
getItemProps,
|
||||||
|
highlightedIndex,
|
||||||
openMenu,
|
openMenu,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
} = useCombobox({
|
} = useCombobox({
|
||||||
|
@ -66,6 +68,7 @@ function CountryInput({ setValue }: CountryInputProps) {
|
||||||
openMenu()
|
openMenu()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ref: inputRef,
|
||||||
})}
|
})}
|
||||||
className="form-control"
|
className="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -86,6 +89,8 @@ function CountryInput({ setValue }: CountryInputProps) {
|
||||||
<div
|
<div
|
||||||
className={classnames('ui-select-choices-row', {
|
className={classnames('ui-select-choices-row', {
|
||||||
active: selectedItem?.name === item.name,
|
active: selectedItem?.name === item.name,
|
||||||
|
'ui-select-choices-row--highlighted':
|
||||||
|
highlightedIndex === index,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span className="ui-select-choices-row-inner">
|
<span className="ui-select-choices-row-inner">
|
||||||
|
@ -99,4 +104,11 @@ function CountryInput({ setValue }: CountryInputProps) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CountryInput = forwardRef<
|
||||||
|
HTMLInputElement,
|
||||||
|
Omit<CountryInputProps, 'inputRef'>
|
||||||
|
>((props, ref) => <Downshift {...props} inputRef={ref} />)
|
||||||
|
|
||||||
|
CountryInput.displayName = 'CountryInput'
|
||||||
|
|
||||||
export default CountryInput
|
export default CountryInput
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, forwardRef } from 'react'
|
||||||
import { useCombobox } from 'downshift'
|
import { useCombobox } from 'downshift'
|
||||||
import classnames from 'classnames'
|
import classnames from 'classnames'
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ type DownshiftInputProps = {
|
||||||
inputValue: string
|
inputValue: string
|
||||||
label: string
|
label: string
|
||||||
setValue: React.Dispatch<React.SetStateAction<string>>
|
setValue: React.Dispatch<React.SetStateAction<string>>
|
||||||
|
inputRef?: React.ForwardedRef<HTMLInputElement>
|
||||||
} & React.InputHTMLAttributes<HTMLInputElement>
|
} & React.InputHTMLAttributes<HTMLInputElement>
|
||||||
|
|
||||||
const filterItemsByInputValue = (
|
const filterItemsByInputValue = (
|
||||||
|
@ -14,13 +15,14 @@ const filterItemsByInputValue = (
|
||||||
inputValue: DownshiftInputProps['inputValue']
|
inputValue: DownshiftInputProps['inputValue']
|
||||||
) => items.filter(item => item.toLowerCase().includes(inputValue.toLowerCase()))
|
) => items.filter(item => item.toLowerCase().includes(inputValue.toLowerCase()))
|
||||||
|
|
||||||
function DownshiftInput({
|
function Downshift({
|
||||||
items,
|
items,
|
||||||
inputValue,
|
inputValue,
|
||||||
placeholder,
|
placeholder,
|
||||||
label,
|
label,
|
||||||
setValue,
|
setValue,
|
||||||
disabled,
|
disabled,
|
||||||
|
inputRef,
|
||||||
}: DownshiftInputProps) {
|
}: DownshiftInputProps) {
|
||||||
const [inputItems, setInputItems] = useState(items)
|
const [inputItems, setInputItems] = useState(items)
|
||||||
|
|
||||||
|
@ -35,6 +37,7 @@ function DownshiftInput({
|
||||||
getInputProps,
|
getInputProps,
|
||||||
getComboboxProps,
|
getComboboxProps,
|
||||||
getItemProps,
|
getItemProps,
|
||||||
|
highlightedIndex,
|
||||||
openMenu,
|
openMenu,
|
||||||
selectedItem,
|
selectedItem,
|
||||||
} = useCombobox({
|
} = useCombobox({
|
||||||
|
@ -78,6 +81,7 @@ function DownshiftInput({
|
||||||
openMenu()
|
openMenu()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ref: inputRef,
|
||||||
})}
|
})}
|
||||||
className="form-control"
|
className="form-control"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -98,6 +102,8 @@ function DownshiftInput({
|
||||||
<div
|
<div
|
||||||
className={classnames('ui-select-choices-row', {
|
className={classnames('ui-select-choices-row', {
|
||||||
active: selectedItem === item,
|
active: selectedItem === item,
|
||||||
|
'ui-select-choices-row--highlighted':
|
||||||
|
highlightedIndex === index,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<span className="ui-select-choices-row-inner">
|
<span className="ui-select-choices-row-inner">
|
||||||
|
@ -111,4 +117,11 @@ function DownshiftInput({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const DownshiftInput = forwardRef<
|
||||||
|
HTMLInputElement,
|
||||||
|
Omit<DownshiftInputProps, 'inputRef'>
|
||||||
|
>((props, ref) => <Downshift {...props} inputRef={ref} />)
|
||||||
|
|
||||||
|
DownshiftInput.displayName = 'DownshiftInput'
|
||||||
|
|
||||||
export default DownshiftInput
|
export default DownshiftInput
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect, useRef } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { UserEmailData } from '../../../../../../types/user-email'
|
import { UserEmailData } from '../../../../../../types/user-email'
|
||||||
import { Button } from 'react-bootstrap'
|
import { Button } from 'react-bootstrap'
|
||||||
import { isChangingAffiliation } from '../../utils/selectors'
|
import { isChangingAffiliation } from '../../utils/selectors'
|
||||||
import { useUserEmailsContext } from '../../context/user-email-context'
|
import { useUserEmailsContext } from '../../context/user-email-context'
|
||||||
import DownshiftInput from './downshift-input'
|
import DownshiftInput from './downshift-input'
|
||||||
import Icon from '../../../../shared/components/icon'
|
|
||||||
import useAsync from '../../../../shared/hooks/use-async'
|
import useAsync from '../../../../shared/hooks/use-async'
|
||||||
import { getJSON, postJSON } from '../../../../infrastructure/fetch-json'
|
import { getJSON, postJSON } from '../../../../infrastructure/fetch-json'
|
||||||
import { defaults as defaultRoles } from '../../roles'
|
import { defaults as defaultRoles } from '../../roles'
|
||||||
|
@ -30,11 +29,22 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
|
||||||
const [role, setRole] = useState(affiliation?.role || '')
|
const [role, setRole] = useState(affiliation?.role || '')
|
||||||
const [department, setDepartment] = useState(affiliation?.department || '')
|
const [department, setDepartment] = useState(affiliation?.department || '')
|
||||||
const [departments, setDepartments] = useState(defaultDepartments)
|
const [departments, setDepartments] = useState(defaultDepartments)
|
||||||
|
const roleRef = useRef<HTMLInputElement | null>(null)
|
||||||
|
const isChangingAffiliationInProgress = isChangingAffiliation(
|
||||||
|
state,
|
||||||
|
userEmailData.email
|
||||||
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setUserEmailsContextLoading(isLoading)
|
setUserEmailsContextLoading(isLoading)
|
||||||
}, [setUserEmailsContextLoading, isLoading])
|
}, [setUserEmailsContextLoading, isLoading])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isChangingAffiliationInProgress && roleRef.current) {
|
||||||
|
roleRef.current?.focus()
|
||||||
|
}
|
||||||
|
}, [roleRef, isChangingAffiliationInProgress])
|
||||||
|
|
||||||
const handleChangeAffiliation = () => {
|
const handleChangeAffiliation = () => {
|
||||||
setEmailAffiliationBeingEdited(userEmailData.email)
|
setEmailAffiliationBeingEdited(userEmailData.email)
|
||||||
|
|
||||||
|
@ -87,7 +97,7 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>{affiliation.institution.name}</div>
|
<div>{affiliation.institution.name}</div>
|
||||||
{!isChangingAffiliation(state, userEmailData.email) ? (
|
{!isChangingAffiliationInProgress ? (
|
||||||
<div className="small">
|
<div className="small">
|
||||||
{(affiliation.role || affiliation.department) && (
|
{(affiliation.role || affiliation.department) && (
|
||||||
<>
|
<>
|
||||||
|
@ -112,6 +122,7 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
|
||||||
placeholder={t('role')}
|
placeholder={t('role')}
|
||||||
label={t('role')}
|
label={t('role')}
|
||||||
setValue={setRole}
|
setValue={setRole}
|
||||||
|
ref={roleRef}
|
||||||
/>
|
/>
|
||||||
<DownshiftInput
|
<DownshiftInput
|
||||||
items={departments}
|
items={departments}
|
||||||
|
@ -130,7 +141,7 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
|
||||||
</Button>
|
</Button>
|
||||||
{!isLoading && (
|
{!isLoading && (
|
||||||
<>
|
<>
|
||||||
<span className="mx-2">{t('save_or_cancel-or')}</span>
|
<span className="mx-1">{t('save_or_cancel-or')}</span>
|
||||||
<Button
|
<Button
|
||||||
className="btn-inline-link"
|
className="btn-inline-link"
|
||||||
onClick={handleCancelAffiliationChange}
|
onClick={handleCancelAffiliationChange}
|
||||||
|
@ -143,10 +154,9 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isError && (
|
{isError && (
|
||||||
<span className="text-danger small">
|
<div className="text-danger small">
|
||||||
<Icon type="exclamation-triangle" fw />{' '}
|
{t('generic_something_went_wrong')}
|
||||||
{t('error_performing_request')}
|
</div>
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -53,10 +53,7 @@ function ResendConfirmationEmailButton({
|
||||||
</Button>
|
</Button>
|
||||||
<br />
|
<br />
|
||||||
{isError && (
|
{isError && (
|
||||||
<span className="text-danger">
|
<div className="text-danger">{t('generic_something_went_wrong')}</div>
|
||||||
<Icon type="exclamation-triangle" fw />{' '}
|
|
||||||
{t('error_performing_request')}
|
|
||||||
</span>
|
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
@ -17,14 +17,14 @@ function EmailsRow({ userEmailData }: EmailsRowProps) {
|
||||||
<Email userEmailData={userEmailData} />
|
<Email userEmailData={userEmailData} />
|
||||||
</EmailCell>
|
</EmailCell>
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={4}>
|
<Col md={5}>
|
||||||
{userEmailData.affiliation?.institution && (
|
{userEmailData.affiliation?.institution && (
|
||||||
<EmailCell>
|
<EmailCell>
|
||||||
<InstitutionAndRole userEmailData={userEmailData} />
|
<InstitutionAndRole userEmailData={userEmailData} />
|
||||||
</EmailCell>
|
</EmailCell>
|
||||||
)}
|
)}
|
||||||
</Col>
|
</Col>
|
||||||
<Col md={4}>
|
<Col md={3}>
|
||||||
<EmailCell className="text-md-right">
|
<EmailCell className="text-md-right">
|
||||||
<Actions userEmailData={userEmailData} />
|
<Actions userEmailData={userEmailData} />
|
||||||
</EmailCell>
|
</EmailCell>
|
||||||
|
|
|
@ -196,7 +196,7 @@ const reducer = (state: State, action: Action) => {
|
||||||
function useUserEmails() {
|
function useUserEmails() {
|
||||||
const [state, unsafeDispatch] = useReducer(reducer, initialState)
|
const [state, unsafeDispatch] = useReducer(reducer, initialState)
|
||||||
const dispatch = useSafeDispatch(unsafeDispatch)
|
const dispatch = useSafeDispatch(unsafeDispatch)
|
||||||
const { data, isLoading, isError, runAsync } = useAsync()
|
const { data, isLoading, isError, isSuccess, runAsync } = useAsync()
|
||||||
|
|
||||||
const getEmails = useCallback(() => {
|
const getEmails = useCallback(() => {
|
||||||
runAsync<UserEmailData[]>(getJSON('/user/emails?ensureAffiliation=true'))
|
runAsync<UserEmailData[]>(getJSON('/user/emails?ensureAffiliation=true'))
|
||||||
|
@ -214,6 +214,7 @@ function useUserEmails() {
|
||||||
return {
|
return {
|
||||||
state,
|
state,
|
||||||
isInitializing: isLoading && !data,
|
isInitializing: isLoading && !data,
|
||||||
|
isInitializingSuccess: isSuccess,
|
||||||
isInitializingError: isError,
|
isInitializingError: isError,
|
||||||
getEmails,
|
getEmails,
|
||||||
setLoading: useCallback(
|
setLoading: useCallback(
|
||||||
|
|
|
@ -26,7 +26,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.ui-select-choices-row:hover {
|
.ui-select-choices-row:hover,
|
||||||
|
.ui-select-choices-row--highlighted {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,19 +35,19 @@
|
||||||
|
|
||||||
/* Mark invalid Select2 */
|
/* Mark invalid Select2 */
|
||||||
.ng-dirty.ng-invalid > a.select2-choice {
|
.ng-dirty.ng-invalid > a.select2-choice {
|
||||||
border-color: #D44950;
|
border-color: #D44950;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select2-result-single {
|
.select2-result-single {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select2-locked > .select2-search-choice-close{
|
.select2-locked > .select2-search-choice-close {
|
||||||
display:none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-locked > .ui-select-match-close{
|
.select-locked > .ui-select-match-close {
|
||||||
display:none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
body > .select2-container.open {
|
body > .select2-container.open {
|
||||||
|
@ -56,46 +57,49 @@ body > .select2-container.open {
|
||||||
/* Handle up direction Select2 */
|
/* Handle up direction Select2 */
|
||||||
.ui-select-container[theme="select2"].direction-up .ui-select-match,
|
.ui-select-container[theme="select2"].direction-up .ui-select-match,
|
||||||
.ui-select-container.select2.direction-up .ui-select-match {
|
.ui-select-container.select2.direction-up .ui-select-match {
|
||||||
border-radius: 4px; /* FIXME hardcoded value :-/ */
|
border-radius: 4px; /* FIXME hardcoded value :-/ */
|
||||||
border-top-left-radius: 0;
|
border-top-left-radius: 0;
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-container[theme="select2"].direction-up .ui-select-dropdown,
|
.ui-select-container[theme="select2"].direction-up .ui-select-dropdown,
|
||||||
.ui-select-container.select2.direction-up .ui-select-dropdown {
|
.ui-select-container.select2.direction-up .ui-select-dropdown {
|
||||||
border-radius: 4px; /* FIXME hardcoded value :-/ */
|
border-radius: 4px; /* FIXME hardcoded value :-/ */
|
||||||
border-bottom-left-radius: 0;
|
border-bottom-left-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
|
|
||||||
border-top-width: 1px; /* FIXME hardcoded value :-/ */
|
border-top-width: 1px; /* FIXME hardcoded value :-/ */
|
||||||
border-top-style: solid;
|
border-top-style: solid;
|
||||||
|
|
||||||
box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25);
|
||||||
|
|
||||||
margin-top: -4px; /* FIXME hardcoded value :-/ */
|
margin-top: -4px; /* FIXME hardcoded value :-/ */
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-container[theme="select2"].direction-up .ui-select-dropdown .select2-search,
|
.ui-select-container[theme="select2"].direction-up .ui-select-dropdown .select2-search,
|
||||||
.ui-select-container.select2.direction-up .ui-select-dropdown .select2-search {
|
.ui-select-container.select2.direction-up .ui-select-dropdown .select2-search {
|
||||||
margin-top: 4px; /* FIXME hardcoded value :-/ */
|
margin-top: 4px; /* FIXME hardcoded value :-/ */
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-container[theme="select2"].direction-up.select2-dropdown-open .ui-select-match,
|
.ui-select-container[theme="select2"].direction-up.select2-dropdown-open .ui-select-match,
|
||||||
.ui-select-container.select2.direction-up.select2-dropdown-open .ui-select-match {
|
.ui-select-container.select2.direction-up.select2-dropdown-open .ui-select-match {
|
||||||
border-bottom-color: #5897fb;
|
border-bottom-color: #5897fb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden,
|
.ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden,
|
||||||
.ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden input{
|
.ui-select-container[theme="select2"] .ui-select-dropdown .ui-select-search-hidden input {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border:0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Selectize theme */
|
/* Selectize theme */
|
||||||
|
|
||||||
/* Helper class to show styles when focus */
|
/* Helper class to show styles when focus */
|
||||||
.selectize-input.selectize-focus{
|
.selectize-input.selectize-focus {
|
||||||
border-color: #007FBB !important;
|
border-color: #007FBB !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,23 +120,23 @@ body > .select2-container.open {
|
||||||
|
|
||||||
/* Mark invalid Selectize */
|
/* Mark invalid Selectize */
|
||||||
.ng-dirty.ng-invalid > div.selectize-input {
|
.ng-dirty.ng-invalid > div.selectize-input {
|
||||||
border-color: #D44950;
|
border-color: #D44950;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle up direction Selectize */
|
/* Handle up direction Selectize */
|
||||||
.ui-select-container[theme="selectize"].direction-up .ui-select-dropdown {
|
.ui-select-container[theme="selectize"].direction-up .ui-select-dropdown {
|
||||||
box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25);
|
||||||
margin-top: -2px; /* FIXME hardcoded value :-/ */
|
margin-top: -2px; /* FIXME hardcoded value :-/ */
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-container[theme="selectize"] input.ui-select-search-hidden{
|
.ui-select-container[theme="selectize"] input.ui-select-search-hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border:0;
|
border: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Bootstrap theme */
|
/* Bootstrap theme */
|
||||||
|
@ -171,22 +175,23 @@ body > .select2-container.open {
|
||||||
border-top-right-radius: 0;
|
border-top-right-radius: 0;
|
||||||
border-bottom-right-radius: 0;
|
border-bottom-right-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group > .ui-select-bootstrap > input.ui-select-search.form-control.direction-up {
|
.input-group > .ui-select-bootstrap > input.ui-select-search.form-control.direction-up {
|
||||||
border-radius: 4px !important; /* FIXME hardcoded value :-/ */
|
border-radius: 4px !important; /* FIXME hardcoded value :-/ */
|
||||||
border-top-right-radius: 0 !important;
|
border-top-right-radius: 0 !important;
|
||||||
border-bottom-right-radius: 0 !important;
|
border-bottom-right-radius: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-bootstrap .ui-select-search-hidden{
|
.ui-select-bootstrap .ui-select-search-hidden {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
height: 0;
|
height: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
border:0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-bootstrap > .ui-select-match > .btn{
|
.ui-select-bootstrap > .ui-select-match > .btn {
|
||||||
/* Instead of center because of .btn */
|
/* Instead of center because of .btn */
|
||||||
text-align: left !important;
|
text-align: left !important;
|
||||||
}
|
}
|
||||||
|
@ -198,7 +203,7 @@ body > .select2-container.open {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* See Scrollable Menu with Bootstrap 3 http://stackoverflow.com/questions/19227496 */
|
/* See Scrollable Menu with Bootstrap 3 http://stackoverflow.com/questions/19227496 */
|
||||||
.ui-select-bootstrap > .ui-select-choices ,.ui-select-bootstrap > .ui-select-no-choice {
|
.ui-select-bootstrap > .ui-select-choices, .ui-select-bootstrap > .ui-select-no-choice {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
max-height: 200px;
|
max-height: 200px;
|
||||||
|
@ -261,62 +266,64 @@ body > .ui-select-bootstrap.open {
|
||||||
border-right: 1px solid #428bca;
|
border-right: 1px solid #428bca;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-bootstrap .ui-select-choices-row>span {
|
.ui-select-bootstrap .ui-select-choices-row > span {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: block;
|
display: block;
|
||||||
padding: 3px 20px;
|
padding: 3px 20px;
|
||||||
clear: both;
|
clear: both;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.42857143;
|
line-height: 1.42857143;
|
||||||
color: #333;
|
color: #333;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-bootstrap .ui-select-choices-row>span:hover, .ui-select-bootstrap .ui-select-choices-row>span:focus {
|
.ui-select-bootstrap .ui-select-choices-row > span:hover, .ui-select-bootstrap .ui-select-choices-row > span:focus {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #262626;
|
color: #262626;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-bootstrap .ui-select-choices-row.active>span {
|
.ui-select-bootstrap .ui-select-choices-row.active > span {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
outline: 0;
|
outline: 0;
|
||||||
background-color: #428bca;
|
background-color: #428bca;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-bootstrap .ui-select-choices-row.disabled>span,
|
.ui-select-bootstrap .ui-select-choices-row.disabled > span,
|
||||||
.ui-select-bootstrap .ui-select-choices-row.active.disabled>span {
|
.ui-select-bootstrap .ui-select-choices-row.active.disabled > span {
|
||||||
color: #777;
|
color: #777;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fix hide/show angular animation */
|
/* fix hide/show angular animation */
|
||||||
.ui-select-match.ng-hide-add,
|
.ui-select-match.ng-hide-add,
|
||||||
.ui-select-search.ng-hide-add {
|
.ui-select-search.ng-hide-add {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mark invalid Bootstrap */
|
/* Mark invalid Bootstrap */
|
||||||
.ui-select-bootstrap.ng-dirty.ng-invalid > button.btn.ui-select-match {
|
.ui-select-bootstrap.ng-dirty.ng-invalid > button.btn.ui-select-match {
|
||||||
border-color: #D44950;
|
border-color: #D44950;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle up direction Bootstrap */
|
/* Handle up direction Bootstrap */
|
||||||
.ui-select-container[theme="bootstrap"].direction-up .ui-select-dropdown {
|
.ui-select-container[theme="bootstrap"].direction-up .ui-select-dropdown {
|
||||||
box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25);
|
box-shadow: 0 -4px 8px rgba(0, 0, 0, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-bootstrap .ui-select-match-text {
|
.ui-select-bootstrap .ui-select-match-text {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-right: 1em;
|
padding-right: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-bootstrap .ui-select-match-text span {
|
.ui-select-bootstrap .ui-select-match-text span {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ui-select-bootstrap .ui-select-toggle > a.btn {
|
.ui-select-bootstrap .ui-select-toggle > a.btn {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
|
@ -326,10 +333,10 @@ body > .ui-select-bootstrap.open {
|
||||||
|
|
||||||
/* Spinner */
|
/* Spinner */
|
||||||
.ui-select-refreshing.glyphicon {
|
.ui-select-refreshing.glyphicon {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
padding: 8px 27px;
|
padding: 8px 27px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@-webkit-keyframes ui-select-spin {
|
@-webkit-keyframes ui-select-spin {
|
||||||
0% {
|
0% {
|
||||||
|
@ -341,6 +348,7 @@ body > .ui-select-bootstrap.open {
|
||||||
transform: rotate(359deg);
|
transform: rotate(359deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes ui-select-spin {
|
@keyframes ui-select-spin {
|
||||||
0% {
|
0% {
|
||||||
-webkit-transform: rotate(0deg);
|
-webkit-transform: rotate(0deg);
|
||||||
|
|
|
@ -65,7 +65,7 @@ describe('email actions - make primary', function () {
|
||||||
screen.getByRole('button', { name: /sending/i })
|
screen.getByRole('button', { name: /sending/i })
|
||||||
)
|
)
|
||||||
|
|
||||||
screen.getByText(/an error has occurred while performing your request/i)
|
screen.getByText(/sorry, something went wrong/i)
|
||||||
screen.getByRole('button', { name: /make primary/i })
|
screen.getByRole('button', { name: /make primary/i })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -114,7 +114,7 @@ describe('email actions - delete', function () {
|
||||||
screen.getByRole('button', { name: /deleting/i })
|
screen.getByRole('button', { name: /deleting/i })
|
||||||
)
|
)
|
||||||
|
|
||||||
screen.getByText(/an error has occurred while performing your request/i)
|
screen.getByText(/sorry, something went wrong/i)
|
||||||
screen.getByRole('button', { name: /remove/i })
|
screen.getByRole('button', { name: /remove/i })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -60,6 +60,7 @@ describe('<EmailsSection />', function () {
|
||||||
hasSamlFeature: true,
|
hasSamlFeature: true,
|
||||||
samlInitPath: 'saml/init',
|
samlInitPath: 'saml/init',
|
||||||
})
|
})
|
||||||
|
fetchMock.reset()
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
|
@ -67,10 +68,12 @@ describe('<EmailsSection />', function () {
|
||||||
resetFetchMock()
|
resetFetchMock()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders "add another email" button', function () {
|
it('renders "add another email" button', async function () {
|
||||||
fetchMock.get('/user/emails?ensureAffiliation=true', [])
|
fetchMock.get('/user/emails?ensureAffiliation=true', [])
|
||||||
render(<EmailsSection />)
|
render(<EmailsSection />)
|
||||||
|
|
||||||
|
await fetchMock.flush(true)
|
||||||
|
|
||||||
screen.getByRole('button', { name: /add another email/i })
|
screen.getByRole('button', { name: /add another email/i })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -146,7 +149,7 @@ describe('<EmailsSection />', function () {
|
||||||
resetFetchMock()
|
resetFetchMock()
|
||||||
fetchMock
|
fetchMock
|
||||||
.get('/user/emails?ensureAffiliation=true', [])
|
.get('/user/emails?ensureAffiliation=true', [])
|
||||||
.post('/user/emails', 500)
|
.post('/user/emails', 400)
|
||||||
|
|
||||||
const addAnotherEmailBtn = screen.getByRole('button', {
|
const addAnotherEmailBtn = screen.getByRole('button', {
|
||||||
name: /add another email/i,
|
name: /add another email/i,
|
||||||
|
@ -170,18 +173,20 @@ describe('<EmailsSection />', function () {
|
||||||
expect(submitBtn.disabled).to.be.true
|
expect(submitBtn.disabled).to.be.true
|
||||||
|
|
||||||
await screen.findByText(
|
await screen.findByText(
|
||||||
/an error has occurred while performing your request/i
|
/Invalid Request. Please correct the data and try again./i
|
||||||
)
|
)
|
||||||
expect(submitBtn).to.not.be.null
|
expect(submitBtn).to.not.be.null
|
||||||
expect(submitBtn.disabled).to.be.false
|
expect(submitBtn.disabled).to.be.false
|
||||||
})
|
})
|
||||||
|
|
||||||
it('can link email address to an existing SSO institution', async function () {
|
it('can link email address to an existing SSO institution', async function () {
|
||||||
fetchMock.reset()
|
|
||||||
fetchMock.get('/user/emails?ensureAffiliation=true', [])
|
fetchMock.get('/user/emails?ensureAffiliation=true', [])
|
||||||
fetchMock.get('express:/institutions/domains', institutionDomainData)
|
|
||||||
render(<EmailsSection />)
|
render(<EmailsSection />)
|
||||||
|
|
||||||
|
await fetchMock.flush(true)
|
||||||
|
fetchMock.reset()
|
||||||
|
fetchMock.get('express:/institutions/domains', institutionDomainData)
|
||||||
|
|
||||||
await userEvent.click(
|
await userEvent.click(
|
||||||
screen.getByRole('button', {
|
screen.getByRole('button', {
|
||||||
name: /add another email/i,
|
name: /add another email/i,
|
||||||
|
|
|
@ -198,7 +198,7 @@ describe('<EmailsSection />', function () {
|
||||||
|
|
||||||
await waitForElementToBeRemoved(() => screen.getByText(/sending/i))
|
await waitForElementToBeRemoved(() => screen.getByText(/sending/i))
|
||||||
|
|
||||||
screen.getByText(/an error has occurred while performing your request/i)
|
screen.getByText(/sorry, something went wrong/i)
|
||||||
screen.getByRole('button', { name: /resend confirmation email/i })
|
screen.getByRole('button', { name: /resend confirmation email/i })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue