mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 18:33:37 -05:00
[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:
parent
bce645b0f1
commit
0e782d3fb6
5 changed files with 143 additions and 11 deletions
|
@ -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": "",
|
||||||
|
|
|
@ -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}>
|
||||||
|
|
12
services/web/frontend/js/shared/utils/email.tsx
Normal file
12
services/web/frontend/js/shared/utils/email.tsx
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 />)
|
||||||
|
|
46
services/web/test/frontend/shared/utils/email.test.tsx
Normal file
46
services/web/test/frontend/shared/utils/email.test.tsx
Normal 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 '
|
||||||
|
)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue