Browse Source

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

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

GitOrigin-RevId: 19d432c70b173752ee7c6d8978dd6be16b042921
Miguel Serrano 4 years ago
parent
commit
0e782d3fb6

+ 1 - 0
services/web/frontend/extracted-translations.json

@@ -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": "",

+ 36 - 11
services/web/frontend/js/features/settings/components/emails/add-email.tsx

@@ -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}>
-          <Cell>
-            <label htmlFor="affiliations-email" className="sr-only">
-              {t('email')}
-            </label>
-            <Input
-              onChange={handleEmailChange}
-              handleAddNewEmail={handleAddNewEmail}
-            />
-          </Cell>
-        </Col>
+        {InputCol}
         {newEmailMatchedDomain &&
         {newEmailMatchedDomain &&
         ssoAvailableForDomain(newEmailMatchedDomain) ? (
         ssoAvailableForDomain(newEmailMatchedDomain) ? (
           <Col md={8}>
           <Col md={8}>

+ 12 - 0
services/web/frontend/js/shared/utils/email.tsx

@@ -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)
+  }
+}

+ 48 - 0
services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx

@@ -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 - 0
services/web/test/frontend/shared/utils/email.test.tsx

@@ -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 '
+      )
+    )
+  })
+})