Преглед изворни кода

Merge pull request #22609 from overleaf/mf-migrate-aux-password-pages-to-bs5

[web] Migrate reset password page to bootstrap 5 and website redesign

GitOrigin-RevId: 613865379d094b305c6dc78f74dc70341214f4ed
M Fahru пре 1 година
родитељ
комит
7fbcca6ed1

+ 23 - 12
services/web/app/src/Features/PasswordReset/PasswordResetController.mjs

@@ -8,6 +8,7 @@ import UserSessionsManager from '../User/UserSessionsManager.js'
 import OError from '@overleaf/o-error'
 import EmailsHelper from '../Helpers/EmailHelper.js'
 import { expressify } from '@overleaf/promise-utils'
+import SplitTestHandler from '../SplitTests/SplitTestHandler.js'
 
 async function setNewUserPassword(req, res, next) {
   let user
@@ -191,19 +192,29 @@ async function renderSetPasswordForm(req, res, next) {
   })
 }
 
-export default {
-  renderRequestResetForm(req, res) {
-    const errorQuery = req.query.error
-    let error = null
-    if (errorQuery === 'token_expired') {
-      error = 'password_reset_token_expired'
-    }
-    res.render('user/passwordReset', {
-      title: 'reset_password',
-      error,
-    })
-  },
+async function renderRequestResetForm(req, res) {
+  const errorQuery = req.query.error
+  let error = null
+  if (errorQuery === 'token_expired') {
+    error = 'password_reset_token_expired'
+  }
+  const { variant } = await SplitTestHandler.promises.getAssignment(
+    req,
+    res,
+    'auth-pages-bs5'
+  )
+
+  const template =
+    variant === 'enabled' ? 'user/passwordReset-bs5' : 'user/passwordReset'
+
+  res.render(template, {
+    title: 'reset_password',
+    error,
+  })
+}
 
+export default {
+  renderRequestResetForm: expressify(renderRequestResetForm),
   requestReset: expressify(requestReset),
   renderSetPasswordForm: expressify(renderSetPasswordForm),
   setNewUserPassword: expressify(setNewUserPassword),

+ 5 - 1
services/web/app/views/_mixins/notification.pug

@@ -5,6 +5,10 @@ mixin notificationIcon(type)
 		span.material-symbols(aria-hidden="true") info
 	else if type === 'success'
 		span.material-symbols(aria-hidden="true") check_circle
+	else if type === 'error'
+		span.material-symbols(aria-hidden="true") error
+	else if type === 'warning'
+		span.material-symbols(aria-hidden="true") warning
 
 
 mixin notification(options)
@@ -24,7 +28,7 @@ mixin notification(options)
 				if title
 					p
 						b #{title}
-				| #{content}
+				| !{content}
 			//- TODO: handle action
 			//- if action
 			//- 	.notification-cta

+ 76 - 0
services/web/app/views/user/passwordReset-bs5.pug

@@ -0,0 +1,76 @@
+extends ../layout-website-redesign-bootstrap-5
+include ../_mixins/recaptcha
+include ../_mixins/notification
+
+block vars
+	- var suppressNavbar = true
+	- var suppressFooter = true
+
+block content
+	- var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
+
+	if showCaptcha
+		script(type="text/javascript", nonce=scriptNonce, src="https://www.recaptcha.net/recaptcha/api.js?render=explicit")
+		div(
+			id="recaptcha"
+			class="g-recaptcha"
+			data-sitekey=settings.recaptcha.siteKey
+			data-size="invisible"
+			data-badge="inline"
+		)
+
+	main#main-content(data-ol-captcha-retry-trigger-area="")
+		a.auth-aux-logo(href="/")
+			img(src=buildImgPath("ol-brand/overleaf-o-dark.svg") alt=settings.appName)
+		.auth-aux-container
+			form(
+				data-ol-async-form
+				name="passwordResetForm"
+				action="/user/password/reset"
+				method="POST"
+				captcha=(showCaptcha ? '' : false)
+				captcha-action-name=(showCaptcha ? "passwordReset" : false)
+			)
+				if error === 'password_reset_token_expired'
+					h1.h3.mb-3.mt-0 #{translate("sorry_your_token_expired")}
+					p #{translate('please_request_a_new_password_reset_email_and_follow_the_link')}.
+				else
+					h1.h3.mb-3.mt-0(data-ol-not-sent) #{translate("password_reset_sentence_case")}
+					h1.h3.mb-3.mt-0(hidden data-ol-sent) #{translate("check_your_email")}   
+					p.mb-3.pb-3(data-ol-not-sent) #{translate("enter_your_email_address_below_and_we_will_send_you_a_link_to_reset_your_password")}.
+
+				div(data-ol-not-sent)
+					+formMessages()
+					if error && error !== 'password_reset_token_expired'
+						+notification({ariaLive: 'assertive', type: 'error', className: 'mb-3', content: translate(error)})
+
+					div(data-ol-custom-form-message="no-password-allowed-due-to-sso" hidden)
+						+notification({ariaLive: 'polite', type: 'error', className: 'mb-3', content: translate("you_cant_reset_password_due_to_sso", {}, [{name: 'a', attrs: {href: '/sso-login'}}])})
+					input(type="hidden" name="_csrf" value=csrfToken)
+					.form-group.mb-3
+						label.form-label(for='email') #{translate("email")}
+						input.form-control#email(
+							aria-label="email"
+							type='email'
+							name='email'
+							required
+							autocomplete="username"
+							autofocus
+						)
+					.actions
+						button.btn.btn-primary.w-100.mb-3(
+							type='submit'
+							data-ol-disabled-inflight
+							aria-label=translate('reset_password_sentence_case')
+						)
+							span(data-ol-inflight="idle")
+								| #{translate("reset_password_sentence_case")}
+							span(hidden data-ol-inflight="pending")
+								| #{translate("requesting_password_reset")}…
+						a.btn.btn-ghost.w-100.mb-3(href="/login") #{translate("back_to_log_in")}
+				div(hidden data-ol-sent)
+					p.mb-4 #{translate('password_reset_email_sent')}
+					a.btn.btn-primary.w-100.mb-3(href="/login") #{translate('back_to_log_in')}
+
+			if showCaptcha
+				+recaptchaConditions

+ 1 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/all.scss

@@ -37,6 +37,7 @@
 @import 'group-settings';
 @import 'templates-v2';
 @import 'homepage';
+@import 'auth';
 @import 'login-register';
 @import 'login';
 @import 'register';

+ 24 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/auth.scss

@@ -0,0 +1,24 @@
+.auth-aux-logo {
+  position: absolute;
+  top: var(--spacing-06);
+  left: var(--spacing-06);
+
+  img {
+    height: 40px;
+  }
+}
+
+.auth-aux-container {
+  padding-top: calc($header-height + $spacing-11);
+  max-width: 320px;
+  min-height: 100vh;
+  margin-left: auto;
+  margin-right: auto;
+
+  .recaptcha-branding {
+    @include body-xs;
+
+    padding: 0;
+    text-align: left;
+  }
+}

+ 2 - 0
services/web/locales/en.json

@@ -1485,6 +1485,7 @@
   "password_managed_externally": "Password settings are managed externally",
   "password_reset": "Password Reset",
   "password_reset_email_sent": "You have been sent an email to complete your password reset.",
+  "password_reset_sentence_case": "Password reset",
   "password_reset_token_expired": "Your password reset token has expired. Please request a new password reset email and follow the link there.",
   "password_too_long_please_reset": "Maximum password length exceeded. Please reset your password.",
   "password_updated": "Password updated",
@@ -1761,6 +1762,7 @@
   "resending_confirmation_email": "Resending confirmation email",
   "reset_password": "Reset Password",
   "reset_password_link": "Click this link to reset your password",
+  "reset_password_sentence_case": "Reset password",
   "reset_your_password": "Reset your password",
   "resize": "Resize",
   "resolve": "Resolve",

+ 7 - 0
services/web/test/unit/src/PasswordReset/PasswordResetControllerTests.mjs

@@ -60,6 +60,11 @@ describe('PasswordResetController', function () {
         removeReconfirmFlag: sinon.stub().resolves(),
       },
     }
+    this.SplitTestHandler = {
+      promises: {
+        getAssignment: sinon.stub().resolves('default'),
+      },
+    }
     this.PasswordResetController = await esmock.strict(MODULE_PATH, {
       '@overleaf/settings': this.settings,
       '../../../../app/src/Features/PasswordReset/PasswordResetHandler':
@@ -81,6 +86,8 @@ describe('PasswordResetController', function () {
       '../../../../app/src/Features/User/UserSessionsManager':
         this.UserSessionsManager,
       '../../../../app/src/Features/User/UserUpdater': this.UserUpdater,
+      '../../../../app/src/Features/SplitTests/SplitTestHandler':
+        this.SplitTestHandler,
     })
   })