Merge pull request #8029 from overleaf/ta-settings-fix-7

[SettingsPage] Small Fixes 7

GitOrigin-RevId: 2716fe13af3f5f6b56d6bba47505fad75ba1adbf
This commit is contained in:
Timothée Alby 2022-05-24 09:47:08 +02:00 committed by Copybot
parent 2c62ba29c7
commit 3580ec6db3
14 changed files with 85 additions and 31 deletions

View file

@ -355,7 +355,7 @@
"reconnect": "",
"redirect_to_editor": "",
"reference_error_relink_hint": "",
"reference_sync": "",
"reference_managers": "",
"references_search_hint": "",
"refresh": "",
"refresh_page_after_linking_dropbox": "",

View file

@ -31,7 +31,7 @@ function EmailsSectionContent() {
<Trans i18nKey="change_primary_email_address_instructions">
<strong />
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a href="/learn/how-to/Keeping_your_account_secure" />
<a href="/learn/how-to/Managing_your_Overleaf_emails" />
</Trans>
</p>
<>

View file

@ -114,7 +114,10 @@ function AddEmail() {
<label htmlFor="affiliations-email" className="sr-only">
{t('email')}
</label>
<Input onChange={handleEmailChange} />
<Input
onChange={handleEmailChange}
handleAddNewEmail={handleAddNewEmail}
/>
</Cell>
</Col>
{isSsoAvailable(newEmailMatchedInstitution) ? (

View file

@ -40,9 +40,10 @@ export function clearDomainCache() {
type InputProps = {
onChange: (value: string, institution?: InstitutionInfo) => void
handleAddNewEmail: () => void
}
function Input({ onChange }: InputProps) {
function Input({ onChange, handleAddNewEmail }: InputProps) {
const { signal } = useAbortController()
const inputRef = useRef<HTMLInputElement | null>(null)
@ -126,6 +127,11 @@ function Input({ onChange }: InputProps) {
if (suggestion) {
setInputValueAndResetSuggestion()
} else {
const match = matchLocalAndDomain(inputValue)
if (match.local && match.domain) {
handleAddNewEmail()
}
}
}
@ -134,7 +140,7 @@ function Input({ onChange }: InputProps) {
setInputValueAndResetSuggestion()
}
},
[suggestion]
[inputValue, suggestion, handleAddNewEmail]
)
useEffect(() => {

View file

@ -1,15 +1,15 @@
import { Alert } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
import usePersistedState from '../../../shared/hooks/use-persisted-state'
import { useUserEmailsContext } from '../context/user-email-context'
export function LeaversSurveyAlert() {
const { t } = useTranslation()
const [expirationDate, setExpirationDate] = usePersistedState(
'showInstitutionalLeaversSurveyUntil',
0,
true
)
const {
showInstitutionalLeaversSurveyUntil,
setShowInstitutionalLeaversSurveyUntil,
} = useUserEmailsContext()
const [hide, setHide] = usePersistedState(
'hideInstitutionalLeaversSurvey',
@ -18,11 +18,11 @@ export function LeaversSurveyAlert() {
)
function handleDismiss() {
setExpirationDate(0)
setShowInstitutionalLeaversSurveyUntil(0)
setHide(true)
}
if (Date.now() > expirationDate) {
if (Date.now() > showInstitutionalLeaversSurveyUntil) {
return null
}

View file

@ -64,7 +64,7 @@ function LinkingSection() {
{hasReferencesLinkingSection ? (
<>
<h3 id="references" className="text-capitalize">
{t('reference_sync')}
{t('reference_managers')}
</h3>
<div className="settings-widgets-container">
{referenceLinkingWidgets.map(
@ -146,9 +146,9 @@ function SSOLinkingWidgetContainer({
break
case 'google':
case 'twitter':
description = t('login_with_service', {
description = `${t('login_with_service', {
service: subscription.provider.name,
})
})}.`
break
case 'orcid':
description = t('oauth_orcid_description')

View file

@ -203,10 +203,10 @@ const reducer = (state: State, action: Action) => {
}
function useUserEmails() {
const [, setExpirationDate] = usePersistedState(
'showInstitutionalLeaversSurveyUntil',
0
)
const [
showInstitutionalLeaversSurveyUntil,
setShowInstitutionalLeaversSurveyUntil,
] = usePersistedState('showInstitutionalLeaversSurveyUntil', 0, true)
const [state, unsafeDispatch] = useReducer(reducer, initialState)
const dispatch = useSafeDispatch(unsafeDispatch)
const { data, isLoading, isError, isSuccess, runAsync } =
@ -237,11 +237,11 @@ function useUserEmails() {
userEmail.emailHasInstitutionLicence
)
if (!stillHasLicenseAccess) {
setExpirationDate(Date.now() + ONE_WEEK_IN_MS)
setShowInstitutionalLeaversSurveyUntil(Date.now() + ONE_WEEK_IN_MS)
}
}
},
[state, setExpirationDate]
[state, setShowInstitutionalLeaversSurveyUntil]
)
return {
@ -250,6 +250,8 @@ function useUserEmails() {
isInitializingSuccess: isSuccess,
isInitializingError: isError,
getEmails,
showInstitutionalLeaversSurveyUntil,
setShowInstitutionalLeaversSurveyUntil,
resetLeaversSurveyExpiration,
setLoading: useCallback(
(flag: boolean) => dispatch(ActionCreators.setLoading(flag)),

View file

@ -97,6 +97,7 @@ export function defaultSetupMocks(fetchMock) {
})
.get(/\/institutions\/domains\?hostname=a/, fakeInstitutionDomain1)
.get(/\/institutions\/domains\?hostname=f/, fakeInstitutionDomain2)
.get(/\/institutions\/domains/, [])
.post(/\/user\/emails\/*/, 200, {
delay: MOCK_DELAY,
})

View file

@ -1,4 +1,5 @@
import EmailsSection from '../../js/features/settings/components/emails-section'
import { UserEmailsProvider } from '../../js/features/settings/context/user-email-context'
import { LeaversSurveyAlert } from '../../js/features/settings/components/leavers-survey-alert'
import localStorage from '../../js/infrastructure/local-storage'
@ -7,7 +8,11 @@ export const SurveyAlert = () => {
'showInstitutionalLeaversSurveyUntil',
Date.now() + 1000 * 60 * 60
)
return <LeaversSurveyAlert />
return (
<UserEmailsProvider>
<LeaversSurveyAlert />
</UserEmailsProvider>
)
}
export default {

View file

@ -21,6 +21,7 @@ import {
defaultSetupMocks as defaultSetupLinkingMocks,
} from './helpers/linking'
import { UserProvider } from '../../js/shared/context/user-context'
import { ScopeDecorator } from '../decorators/scope'
export const Overleaf = args => {
setDefaultLeaveMeta()
@ -70,4 +71,5 @@ export const ServerPro = args => {
export default {
title: 'Account Settings / Full Page',
component: SettingsPageRoot,
decorators: [ScopeDecorator],
}

View file

@ -263,7 +263,7 @@
"dropbox_already_linked_error_with_email": "Your Dropbox account cannot be linked as it is already linked with another Overleaf account using email address __otherUsersEmail__.",
"github_too_many_files_error": "This repository cannot be imported as it exceeds the maximum number of files allowed",
"linked_accounts": "linked accounts",
"linked_accounts_explained": "You can link your __appName__ account with other services to enable the features described below",
"linked_accounts_explained": "You can link your __appName__ account with other services to enable the features described below.",
"oauth_orcid_description": " <a href=\"__link__\">Securely establish your identity by linking your ORCID iD to your __appName__ account</a>. Submissions to participating publishers will automatically include your ORCID iD for improved workflow and visibility. ",
"no_existing_password": "Please use the password reset form to set your password",
"password_managed_externally": "Password settings are managed externally",
@ -477,6 +477,7 @@
"reference_search": "Advanced reference search",
"reference_search_info": "You can always search by citation key, and advanced reference search lets you also search by author, title, year or journal.",
"reference_search_info_v2": "Its easy to find your references - you can search by author, title, year or journal. You can still search by citation key too.",
"reference_managers": "Reference managers",
"reference_sync": "Reference manager sync",
"reference_sync_info": "Manage your reference library in Mendeley and link it directly to a .bib file in Overleaf, so you can easily cite anything in your Mendeley library.",
"faq_how_free_trial_works_answer": "You get full access to your chosen __appName__ plan during your __len__-day free trial. There is no obligation to continue beyond the trial. Your card will be charged at the end of your __len__ day trial unless you cancel before then. You can cancel via your subscription settings.",

View file

@ -18,6 +18,7 @@ const testInstitutionData = [
describe('<AddEmailInput/>', function () {
const defaultProps = {
onChange: (value: string) => {},
handleAddNewEmail: () => {},
}
beforeEach(function () {
@ -40,11 +41,19 @@ describe('<AddEmailInput/>', function () {
describe('when typing text that does not contain any potential domain match', function () {
let onChangeStub
let handleAddNewEmailStub
beforeEach(function () {
fetchMock.get('express:/institutions/domains', 200)
onChangeStub = sinon.stub()
render(<Input {...defaultProps} onChange={onChangeStub} />)
handleAddNewEmailStub = sinon.stub()
render(
<Input
{...defaultProps}
onChange={onChangeStub}
handleAddNewEmail={handleAddNewEmailStub}
/>
)
fireEvent.change(screen.getByRole('textbox'), {
target: { value: 'user' },
})
@ -66,6 +75,22 @@ describe('<AddEmailInput/>', function () {
it('should not make any request for institution domains', function () {
expect(fetchMock.called()).to.be.false
})
it('should submit on Enter if email looks valid', async function () {
fireEvent.change(screen.getByRole('textbox'), {
target: { value: 'user@domain.com' },
})
fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Enter' })
expect(handleAddNewEmailStub.calledWith()).to.equal(true)
})
it('should not submit on Enter if email does not look valid', async function () {
fireEvent.change(screen.getByRole('textbox'), {
target: { value: 'user@' },
})
fireEvent.keyDown(screen.getByRole('textbox'), { key: 'Enter' })
expect(handleAddNewEmailStub.calledWith()).to.equal(false)
})
})
describe('when typing text that contains a potential domain match', function () {
@ -73,7 +98,7 @@ describe('<AddEmailInput/>', function () {
beforeEach(function () {
onChangeStub = sinon.stub()
render(<Input onChange={onChangeStub} />)
render(<Input {...defaultProps} onChange={onChangeStub} />)
})
describe('when there are no matches', function () {

View file

@ -1,14 +1,23 @@
import { expect } from 'chai'
import { fireEvent, screen, render } from '@testing-library/react'
import { UserEmailsProvider } from '../../../../../frontend/js/features/settings/context/user-email-context'
import { LeaversSurveyAlert } from '../../../../../frontend/js/features/settings/components/leavers-survey-alert'
import localStorage from '../../../../../frontend/js/infrastructure/local-storage'
function renderWithProvider() {
render(<LeaversSurveyAlert />, {
wrapper: ({ children }) => (
<UserEmailsProvider>{children}</UserEmailsProvider>
),
})
}
describe('<LeaversSurveyAlert/>', function () {
it('should render before the expiration date', function () {
const tomorrow = Date.now() + 1000 * 60 * 60 * 24
localStorage.setItem('showInstitutionalLeaversSurveyUntil', tomorrow)
localStorage.setItem('hideInstitutionalLeaversSurvey', false)
render(<LeaversSurveyAlert />)
renderWithProvider()
screen.getByRole('alert')
screen.getByText(/Provide some quick feedback/)
screen.getByRole('link', { name: 'Take a short survey' })
@ -18,7 +27,7 @@ describe('<LeaversSurveyAlert/>', function () {
const yesterday = Date.now() - 1000 * 60 * 60 * 24
localStorage.setItem('showInstitutionalLeaversSurveyUntil', yesterday)
localStorage.setItem('hideInstitutionalLeaversSurvey', false)
render(<LeaversSurveyAlert />)
renderWithProvider()
expect(screen.queryByRole('alert')).to.be.null
})
@ -26,7 +35,7 @@ describe('<LeaversSurveyAlert/>', function () {
const tomorrow = Date.now() + 1000 * 60 * 60 * 24
localStorage.setItem('showInstitutionalLeaversSurveyUntil', tomorrow)
localStorage.setItem('hideInstitutionalLeaversSurvey', true)
render(<LeaversSurveyAlert />)
renderWithProvider()
expect(screen.queryByRole('alert')).to.be.null
})
@ -34,7 +43,7 @@ describe('<LeaversSurveyAlert/>', function () {
const tomorrow = Date.now() + 1000 * 60 * 60 * 24
localStorage.setItem('showInstitutionalLeaversSurveyUntil', tomorrow)
localStorage.setItem('hideInstitutionalLeaversSurvey', false)
render(<LeaversSurveyAlert />)
renderWithProvider()
screen.getByRole('alert')
fireEvent.click(screen.getByRole('button'))

View file

@ -65,7 +65,7 @@ describe('<LinkingSection />', function () {
screen.getByText('Integrations')
screen.getByText(
'You can link your Overleaf account with other services to enable the features described below'
'You can link your Overleaf account with other services to enable the features described below.'
)
})
@ -74,7 +74,7 @@ describe('<LinkingSection />', function () {
screen.getByText('linked accounts')
screen.getByText('Google')
screen.getByText('Log in with Google')
screen.getByText('Log in with Google.')
screen.getByRole('button', { name: 'Unlink' })
screen.getByText('Orcid')