Răsfoiți Sursa

Merge pull request #8259 from overleaf/ds-admin-panel-register-new-user

Migrating admin panel register new user to react

GitOrigin-RevId: 520fea91cd9e560e4381504de45e5bedf11a7844
Davinder Singh 4 ani în urmă
părinte
comite
5498d59c6b

+ 0 - 1
services/web/frontend/js/main.js

@@ -23,7 +23,6 @@ import './main/translations'
 import './main/subscription-dashboard'
 import './main/new-subscription'
 import './main/annual-upgrade'
-import './main/register-users'
 import './main/subscription/team-invite-controller'
 import './main/subscription/upgrade-subscription'
 import './main/learn'

+ 0 - 50
services/web/frontend/js/main/register-users.js

@@ -1,50 +0,0 @@
-import _ from 'lodash'
-/* eslint-disable
-    max-len,
-    no-return-assign,
-*/
-// TODO: This file was created by bulk-decaffeinate.
-// Fix any style issues and re-enable lint.
-/*
- * decaffeinate suggestions:
- * DS101: Remove unnecessary use of Array.from
- * DS102: Remove unnecessary code created because of implicit returns
- * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
- */
-import App from '../base'
-
-export default App.controller(
-  'RegisterUsersController',
-  function ($scope, queuedHttp) {
-    $scope.users = []
-
-    $scope.inputs = { emails: '' }
-
-    const parseEmails = function (emailsString) {
-      const regexBySpaceOrComma = /[\s,]+/
-      let emails = emailsString.split(regexBySpaceOrComma)
-      emails = _.map(emails, email => (email = email.trim()))
-      emails = _.filter(emails, email => email.indexOf('@') !== -1)
-      return emails
-    }
-
-    return ($scope.registerUsers = function () {
-      const emails = parseEmails($scope.inputs.emails)
-      $scope.error = false
-      return Array.from(emails).map(email =>
-        queuedHttp
-          .post('/admin/register', {
-            email,
-            _csrf: window.csrfToken,
-          })
-          .then(function (response) {
-            const { data } = response
-            const user = data
-            $scope.users.push(user)
-            return ($scope.inputs.emails = '')
-          })
-          .catch(() => ($scope.error = true))
-      )
-    })
-  }
-)

+ 6 - 35
services/web/modules/user-activate/app/views/user/register.pug

@@ -1,40 +1,11 @@
 extends ../../../../../app/views/layout
 
+block entrypointVar
+	- entrypoint = 'modules/user-activate/pages/user-activate-page'
+
+
 block content
 	.content.content-alt
 		.container
-			.row
-				.col-md-12
-					.card(ng-controller="RegisterUsersController")
-						.page-header
-							h1 Register New Users
-						form.form
-							.row
-								.col-md-4.col-xs-8
-									input.form-control(
-									name="email",
-									type="text",
-									placeholder="jane@example.com, joe@example.com",
-									ng-model="inputs.emails",
-									on-enter="registerUsers()"
-								)
-								.col-md-8.col-xs-4
-									button.btn.btn-primary(ng-click="registerUsers()") #{translate("register")}
-									
-						.row-spaced(ng-show="error").ng-cloak.text-danger
-							p Sorry, an error occured
-						
-						.row-spaced(ng-show="users.length > 0").ng-cloak.text-success
-							p We've sent out welcome emails to the registered users.
-							p You can also manually send them URLs below to allow them to reset their password and log in for the first time.
-							p (Password reset tokens will expire after one week and the user will need registering again).
-
-						hr(ng-show="users.length > 0").ng-cloak
-						table(ng-show="users.length > 0").table.table-striped.ng-cloak
-							tr
-								th #{translate("email")}
-								th Set Password Url
-							tr(ng-repeat="user in users")
-								td {{ user.email }}
-								td(style="word-break: break-all;") {{ user.setNewPasswordUrl }}
-							
+			#user-activate-register-container
+			

+ 87 - 0
services/web/modules/user-activate/frontend/js/components/register-form.js

@@ -0,0 +1,87 @@
+import PropTypes from 'prop-types'
+import { postJSON } from '../../../../../frontend/js/infrastructure/fetch-json'
+
+function RegisterForm({
+  setRegistrationSuccess,
+  setEmails,
+  setRegisterError,
+  setFailedEmails,
+}) {
+  function handleRegister(event) {
+    event.preventDefault()
+    const formData = new FormData(event.target)
+    const formDataAsEntries = formData.entries()
+    const formDataAsObject = Object.fromEntries(formDataAsEntries)
+    const emailString = formDataAsObject.email
+    setRegistrationSuccess(false)
+    setRegisterError(false)
+    setEmails([])
+    registerGivenUsers(parseEmails(emailString))
+  }
+
+  async function registerGivenUsers(emails) {
+    const registeredEmails = []
+    const failingEmails = []
+    for (const email of emails) {
+      try {
+        const result = await registerUser(email)
+        registeredEmails.push(result)
+      } catch {
+        failingEmails.push(email)
+      }
+    }
+    if (registeredEmails.length > 0) setRegistrationSuccess(true)
+    if (failingEmails.length > 0) {
+      setRegisterError(true)
+      setFailedEmails(failingEmails)
+    }
+    setEmails(registeredEmails)
+  }
+
+  function registerUser(email) {
+    const options = { email }
+    const url = `/admin/register`
+    return postJSON(url, { body: options })
+  }
+
+  return (
+    <form onSubmit={handleRegister}>
+      <div className="row">
+        <div className="col-md-4 col-xs-8">
+          <input
+            className="form-control"
+            name="email"
+            type="text"
+            placeholder="jane@example.com, joe@example.com"
+            aria-label="emails to register"
+            aria-describedby="input-details"
+          />
+          <p id="input-details" className="sr-only">
+            Enter the emails you would like to register and separate them using
+            commas
+          </p>
+        </div>
+        <div className="col-md-8 col-xs-4">
+          <button className="btn btn-primary">Register</button>
+        </div>
+      </div>
+    </form>
+  )
+}
+
+function parseEmails(emailsText) {
+  const regexBySpaceOrComma = /[\s,]+/
+  let emails = emailsText.split(regexBySpaceOrComma)
+  emails.map(email => email.trim())
+  emails = emails.filter(email => email.indexOf('@') !== -1)
+  return emails
+}
+
+RegisterForm.propTypes = {
+  setRegistrationSuccess: PropTypes.func,
+  setEmails: PropTypes.func,
+  setRegisterError: PropTypes.func,
+  setFailedEmails: PropTypes.func,
+}
+
+export default RegisterForm

+ 92 - 0
services/web/modules/user-activate/frontend/js/components/user-activate-register.js

@@ -0,0 +1,92 @@
+import { useState } from 'react'
+import PropTypes from 'prop-types'
+import RegisterForm from './register-form'
+function UserActivateRegister() {
+  const [emails, setEmails] = useState([])
+  const [failedEmails, setFailedEmails] = useState([])
+  const [registerError, setRegisterError] = useState(false)
+  const [registrationSuccess, setRegistrationSuccess] = useState(false)
+
+  return (
+    <div className="row">
+      <div className="col-md-12">
+        <div className="card">
+          <div className="page-header">
+            <h1> Register New Users</h1>
+          </div>
+          <RegisterForm
+            setRegistrationSuccess={setRegistrationSuccess}
+            setEmails={setEmails}
+            setRegisterError={setRegisterError}
+            setFailedEmails={setFailedEmails}
+          />
+          {registerError ? (
+            <UserActivateError failedEmails={failedEmails} />
+          ) : null}
+          {registrationSuccess ? (
+            <>
+              <SuccessfulRegistrationMessage />
+              <hr />
+              <DisplayEmailsList emails={emails} />
+            </>
+          ) : null}
+        </div>
+      </div>
+    </div>
+  )
+}
+
+function UserActivateError({ failedEmails }) {
+  return (
+    <div className="row-spaced text-danger">
+      <p>Sorry, an error occured, failed to register these emails.</p>
+      {failedEmails.map(email => (
+        <p key={email}>{email}</p>
+      ))}
+    </div>
+  )
+}
+
+function SuccessfulRegistrationMessage() {
+  return (
+    <div className="row-spaced text-success">
+      <p>We've sent out welcome emails to the registered users.</p>
+      <p>
+        You can also manually send them URLs below to allow them to reset their
+        password and log in for the first time.
+      </p>
+      <p>
+        (Password reset tokens will expire after one week and the user will need
+        registering again).
+      </p>
+    </div>
+  )
+}
+
+function DisplayEmailsList({ emails }) {
+  return (
+    <table className="table table-striped ">
+      <tbody>
+        <tr>
+          <th>Email</th>
+          <th>Set Password Url</th>
+        </tr>
+        {emails.map(user => (
+          <tr key={user.email}>
+            <td>{user.email}</td>
+            <td style={{ wordBreak: 'break-all' }}>{user.setNewPasswordUrl}</td>
+          </tr>
+        ))}
+      </tbody>
+    </table>
+  )
+}
+
+DisplayEmailsList.propTypes = {
+  emails: PropTypes.array,
+}
+UserActivateError.propTypes = {
+  failedEmails: PropTypes.array,
+}
+
+export default UserActivateRegister

+ 9 - 0
services/web/modules/user-activate/frontend/js/pages/user-activate-page.js

@@ -0,0 +1,9 @@
+import '../../../../../frontend/js/marketing'
+
+import ReactDOM from 'react-dom'
+import UserActivateRegister from '../components/user-activate-register'
+
+ReactDOM.render(
+  <UserActivateRegister />,
+  document.getElementById('user-activate-register-container')
+)

+ 62 - 0
services/web/modules/user-activate/test/frontend/js/components/register-form.test.js

@@ -0,0 +1,62 @@
+import { expect } from 'chai'
+import { render, screen, fireEvent } from '@testing-library/react'
+import fetchMock from 'fetch-mock'
+import sinon from 'sinon'
+import RegisterForm from '../../../../frontend/js/components/register-form'
+
+describe('RegisterForm', function () {
+  beforeEach(function () {
+    fetchMock.reset()
+  })
+  afterEach(function () {
+    fetchMock.reset()
+  })
+  it('should render the register form', async function () {
+    const setRegistrationSuccessStub = sinon.stub()
+    const setEmailsStub = sinon.stub()
+    const setRegisterErrorStub = sinon.stub()
+    const setFailedEmailsStub = sinon.stub()
+
+    render(
+      <RegisterForm
+        setRegistrationSuccess={setRegistrationSuccessStub}
+        setEmails={setEmailsStub}
+        setRegisterError={setRegisterErrorStub}
+        setFailedEmails={setFailedEmailsStub}
+      />
+    )
+    await screen.findByLabelText('emails to register')
+    screen.getByRole('button', { name: /register/i })
+  })
+
+  it('should call the fetch request when register button is pressed', async function () {
+    const email = 'abc@gmail.com'
+    const setRegistrationSuccessStub = sinon.stub()
+    const setEmailsStub = sinon.stub()
+    const setRegisterErrorStub = sinon.stub()
+    const setFailedEmailsStub = sinon.stub()
+
+    const endPointResponse = {
+      status: 200,
+      body: {
+        email,
+        setNewPasswordUrl: 'SetNewPasswordURL',
+      },
+    }
+    const registerMock = fetchMock.post('/admin/register', endPointResponse)
+
+    render(
+      <RegisterForm
+        setRegistrationSuccess={setRegistrationSuccessStub}
+        setEmails={setEmailsStub}
+        setRegisterError={setRegisterErrorStub}
+        setFailedEmails={setFailedEmailsStub}
+      />
+    )
+    const registerInput = screen.getByLabelText('emails to register')
+    const registerButton = screen.getByRole('button', { name: /register/i })
+    fireEvent.change(registerInput, { target: { value: email } })
+    fireEvent.click(registerButton)
+    expect(registerMock.called()).to.be.true
+  })
+})

+ 139 - 0
services/web/modules/user-activate/test/frontend/js/components/user-activate-register.test.js

@@ -0,0 +1,139 @@
+import { expect } from 'chai'
+import { render, screen, fireEvent } from '@testing-library/react'
+import fetchMock from 'fetch-mock'
+import UserActivateRegister from '../../../../frontend/js/components/user-activate-register'
+
+describe('UserActivateRegister', function () {
+  beforeEach(function () {
+    fetchMock.reset()
+  })
+  afterEach(function () {
+    fetchMock.reset()
+  })
+  it('should display the error message', async function () {
+    const email = 'abc@gmail.com'
+    render(<UserActivateRegister />)
+    const endPointResponse = {
+      status: 500,
+    }
+    const registerMock = fetchMock.post('/admin/register', endPointResponse)
+    const registerInput = screen.getByLabelText('emails to register')
+    const registerButton = screen.getByRole('button', { name: /register/i })
+
+    fireEvent.change(registerInput, { target: { value: email } })
+    fireEvent.click(registerButton)
+
+    expect(registerMock.called()).to.be.true
+    await screen.findByText('Sorry, an error occured', { exact: false })
+  })
+
+  it('should display the success message', async function () {
+    const email = 'abc@gmail.com'
+    render(<UserActivateRegister />)
+    const endPointResponse = {
+      status: 200,
+      body: {
+        email,
+        setNewPasswordUrl: 'SetNewPasswordURL',
+      },
+    }
+    const registerMock = fetchMock.post('/admin/register', endPointResponse)
+    const registerInput = screen.getByLabelText('emails to register')
+    const registerButton = screen.getByRole('button', { name: /register/i })
+
+    fireEvent.change(registerInput, { target: { value: email } })
+    fireEvent.click(registerButton)
+
+    expect(registerMock.called()).to.be.true
+    await screen.findByText(
+      "We've sent out welcome emails to the registered users."
+    )
+  })
+
+  it('should display the registered emails', async function () {
+    const email = 'abc@gmail.com, def@gmail.com'
+    render(<UserActivateRegister />)
+    const endPointResponse1 = {
+      status: 200,
+      body: {
+        email: 'abc@gmail.com',
+        setNewPasswordUrl: 'SetNewPasswordURL',
+      },
+    }
+    const endPointResponse2 = {
+      status: 200,
+      body: {
+        email: 'def@gmail.com',
+        setNewPasswordUrl: 'SetNewPasswordURL',
+      },
+    }
+    const registerMock = fetchMock.post('/admin/register', (path, req) => {
+      const body = JSON.parse(req.body)
+      if (body.email === 'abc@gmail.com') return endPointResponse1
+      else if (body.email === 'def@gmail.com') return endPointResponse2
+    })
+    const registerInput = screen.getByLabelText('emails to register')
+    const registerButton = screen.getByRole('button', { name: /register/i })
+
+    fireEvent.change(registerInput, { target: { value: email } })
+    fireEvent.click(registerButton)
+
+    expect(registerMock.called()).to.be.true
+    await screen.findByText('abc@gmail.com')
+    await screen.findByText('def@gmail.com')
+  })
+
+  it('should display the failed emails', async function () {
+    const email = 'abc@, def@'
+    render(<UserActivateRegister />)
+    const endPointResponse1 = {
+      status: 500,
+    }
+    const endPointResponse2 = {
+      status: 500,
+    }
+    const registerMock = fetchMock.post('/admin/register', (path, req) => {
+      const body = JSON.parse(req.body)
+      if (body.email === 'abc@') return endPointResponse1
+      else if (body.email === 'def@') return endPointResponse2
+    })
+    const registerInput = screen.getByLabelText('emails to register')
+    const registerButton = screen.getByRole('button', { name: /register/i })
+
+    fireEvent.change(registerInput, { target: { value: email } })
+    fireEvent.click(registerButton)
+
+    expect(registerMock.called()).to.be.true
+    await screen.findByText('abc@')
+    await screen.findByText('def@')
+  })
+
+  it('should display the registered and failed emails together', async function () {
+    const email = 'abc@gmail.com, def@'
+    render(<UserActivateRegister />)
+    const endPointResponse1 = {
+      status: 200,
+      body: {
+        email: 'abc@gmail.com',
+        setNewPasswordUrl: 'SetNewPasswordURL',
+      },
+    }
+    const endPointResponse2 = {
+      status: 500,
+    }
+    const registerMock = fetchMock.post('/admin/register', (path, req) => {
+      const body = JSON.parse(req.body)
+      if (body.email === 'abc@gmail.com') return endPointResponse1
+      else if (body.email === 'def@gmail.com') return endPointResponse2
+    })
+    const registerInput = screen.getByLabelText('emails to register')
+    const registerButton = screen.getByRole('button', { name: /register/i })
+
+    fireEvent.change(registerInput, { target: { value: email } })
+    fireEvent.click(registerButton)
+
+    expect(registerMock.called()).to.be.true
+    await screen.findByText('abc@gmail.com')
+    await screen.findByText('def@')
+  })
+})