[Settings] "Start by adding your email address" hint (#8173)

* [Settings] "Start by adding your email address" hint

GitOrigin-RevId: 19d432c70b173752ee7c6d8978dd6be16b042921
This commit is contained in:
Miguel Serrano 2022-05-30 15:47:58 +02:00 committed by Copybot
parent bce645b0f1
commit 0e782d3fb6
5 changed files with 143 additions and 11 deletions

View file

@ -424,6 +424,7 @@
"somthing_went_wrong_compiling": "", "somthing_went_wrong_compiling": "",
"split_screen": "", "split_screen": "",
"sso_link_error": "", "sso_link_error": "",
"start_by_adding_your_email": "",
"start_free_trial": "", "start_free_trial": "",
"stop_compile": "", "stop_compile": "",
"stop_on_validation_error": "", "stop_on_validation_error": "",

View file

@ -14,6 +14,7 @@ import { ssoAvailableForDomain } from '../../utils/sso'
import { postJSON } from '../../../../infrastructure/fetch-json' import { postJSON } from '../../../../infrastructure/fetch-json'
import { University } from '../../../../../../types/university' import { University } from '../../../../../../types/university'
import { CountryCode } from '../../data/countries-list' import { CountryCode } from '../../data/countries-list'
import { isValidEmail } from '../../../../shared/utils/email'
function AddEmail() { function AddEmail() {
const { t } = useTranslation() const { t } = useTranslation()
@ -106,20 +107,44 @@ function AddEmail() {
) )
} }
const InputCol = (
<Col md={4}>
<Cell>
<label htmlFor="affiliations-email" className="sr-only">
{t('email')}
</label>
<Input
onChange={handleEmailChange}
handleAddNewEmail={handleAddNewEmail}
/>
</Cell>
</Col>
)
if (!isValidEmail(newEmail)) {
return (
<Layout isError={isError} error={error}>
<form>
{InputCol}
<Col md={5}>
<Cell>
<div>{t('start_by_adding_your_email')}</div>
</Cell>
</Col>
<Col md={3}>
<Cell className="text-md-right">
<AddNewEmailBtn email={newEmail} disabled />
</Cell>
</Col>
</form>
</Layout>
)
}
return ( return (
<Layout isError={isError} error={error}> <Layout isError={isError} error={error}>
<form> <form>
<Col md={4}> {InputCol}
<Cell>
<label htmlFor="affiliations-email" className="sr-only">
{t('email')}
</label>
<Input
onChange={handleEmailChange}
handleAddNewEmail={handleAddNewEmail}
/>
</Cell>
</Col>
{newEmailMatchedDomain && {newEmailMatchedDomain &&
ssoAvailableForDomain(newEmailMatchedDomain) ? ( ssoAvailableForDomain(newEmailMatchedDomain) ? (
<Col md={8}> <Col md={8}>

View file

@ -0,0 +1,12 @@
// Copied from backend code: https://github.com/overleaf/internal/blob/6af8ae850bd8075e6bf0ebcafd2731177cdf49ad/services/web/app/src/Features/Helpers/EmailHelper.js#L4
const EMAIL_REGEXP =
// eslint-disable-next-line no-useless-escape
/^([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
export function isValidEmail(email: string | undefined | null) {
if (!email) {
return false
} else {
return EMAIL_REGEXP.test(email)
}
}

View file

@ -92,6 +92,54 @@ describe('<EmailsSection />', function () {
screen.getByLabelText(/email/i) screen.getByLabelText(/email/i)
}) })
it('renders "Start adding your address" until a valid email is typed', async function () {
fetchMock.get('/user/emails?ensureAffiliation=true', [])
render(<EmailsSection />)
const addAnotherEmailBtn = (await screen.findByRole('button', {
name: /add another email/i,
})) as HTMLButtonElement
fireEvent.click(addAnotherEmailBtn)
const input = screen.getByLabelText(/email/i)
// initially the text is displayed and the "add email" button disabled
screen.getByText('Start by adding your email address.')
expect(
(
screen.getByRole('button', {
name: /add new email/i,
}) as HTMLButtonElement
).disabled
).to.be.true
// no changes while writing the email address
fireEvent.change(input, {
target: { value: 'partial@email' },
})
screen.getByText('Start by adding your email address.')
expect(
(
screen.getByRole('button', {
name: /add new email/i,
}) as HTMLButtonElement
).disabled
).to.be.true
// the text is removed when the complete email address is typed, and the "add button" is reenabled
fireEvent.change(input, {
target: { value: 'valid@email.com' },
})
expect(screen.queryByText('Start by adding your email address.')).to.be.null
expect(
(
screen.getByRole('button', {
name: /add new email/i,
}) as HTMLButtonElement
).disabled
).to.be.false
})
it('renders "add new email" button', async function () { it('renders "add new email" button', async function () {
fetchMock.get('/user/emails?ensureAffiliation=true', []) fetchMock.get('/user/emails?ensureAffiliation=true', [])
render(<EmailsSection />) render(<EmailsSection />)

View file

@ -0,0 +1,46 @@
import { expect } from 'chai'
import { isValidEmail } from '../../../../frontend/js/shared/utils/email'
const validEmailAddresses = [
'email@example.com',
'firstname.lastname@example.com',
'firstname-lastname@example.com',
'email@subdomain.example.com',
'firstname+lastname@example.com',
'1234567890@example.com',
'email@example-one.com',
'_@example.com',
'email@example.name',
'email@example.co.jp',
]
const invalidEmailAddresses = [
'plaintext',
'#@%^%#$@#$@#.com',
'@example.com',
'email.example.com',
'.email@example.com',
'email.@example.com',
'email..email@example.com',
'email@example.com (Joe Smith)',
'email@example',
'email@111.222.333.44444',
'email@example..com',
]
describe('isValidEmail', function () {
it('should return true for valid email addresses', function () {
validEmailAddresses.forEach(email =>
expect(isValidEmail(email)).to.equal(true, email + ' should be valid ')
)
})
it('should return false for invalid email addresses', function () {
invalidEmailAddresses.forEach(email =>
expect(isValidEmail(email)).to.equal(
false,
email + ' should not be valid '
)
)
})
})