Pārlūkot izejas kodu

CIAM registration form buttons, inputs and fixes (#29740)

* Many fixes to CIAM registration form, including Phosphor icons

* Unify layout between Pug and React, fixes for spacing and mobile screen sizes

* Pug lint fix

* Make CIAM footer links underlined

* Add CIAM error notification styling

* Merge duplicate style rules

* Remove outdated comment

* Fix ordering of en.json

* Move aria-label to buttons

* Move full stop into translation string

* Remove dummy password strength indicator

* CIAM spacing and label fixes

* Header logo fixes from review

* Add aria-hidden to error icon

GitOrigin-RevId: 87c8181566f0878256b8010f95f115ec25c7ceb9
Tim Down 8 mēneši atpakaļ
vecāks
revīzija
00f6a1e0f9

+ 51 - 3
package-lock.json

@@ -13191,7 +13191,6 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz",
       "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==",
-      "dev": true,
       "license": "BSD-3-Clause"
     },
     "node_modules/@lit/reactive-element": {
@@ -15620,6 +15619,55 @@
         "react-dom": ">= 16.8"
       }
     },
+    "node_modules/@phosphor-icons/webcomponents": {
+      "version": "2.1.5",
+      "resolved": "https://registry.npmjs.org/@phosphor-icons/webcomponents/-/webcomponents-2.1.5.tgz",
+      "integrity": "sha512-JcvQkZxvcX2jK+QCclm8+e8HXqtdFW9xV4/kk2aL9Y3dJA2oQVt+pzbv1orkumz3rfx4K9mn9fDoMr1He1yr7Q==",
+      "license": "MIT",
+      "dependencies": {
+        "lit": "^3"
+      }
+    },
+    "node_modules/@phosphor-icons/webcomponents/node_modules/@lit/reactive-element": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz",
+      "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@lit-labs/ssr-dom-shim": "^1.4.0"
+      }
+    },
+    "node_modules/@phosphor-icons/webcomponents/node_modules/lit": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.1.tgz",
+      "integrity": "sha512-Ksr/8L3PTapbdXJCk+EJVB78jDodUMaP54gD24W186zGRARvwrsPfS60wae/SSCTCNZVPd1chXqio1qHQmu4NA==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@lit/reactive-element": "^2.1.0",
+        "lit-element": "^4.2.0",
+        "lit-html": "^3.3.0"
+      }
+    },
+    "node_modules/@phosphor-icons/webcomponents/node_modules/lit-element": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz",
+      "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@lit-labs/ssr-dom-shim": "^1.4.0",
+        "@lit/reactive-element": "^2.1.0",
+        "lit-html": "^3.3.0"
+      }
+    },
+    "node_modules/@phosphor-icons/webcomponents/node_modules/lit-html": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.1.tgz",
+      "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "@types/trusted-types": "^2.0.2"
+      }
+    },
     "node_modules/@pkgjs/parseargs": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -20579,8 +20627,7 @@
     "node_modules/@types/trusted-types": {
       "version": "2.0.7",
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
-      "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
-      "dev": true
+      "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
     },
     "node_modules/@types/unist": {
       "version": "2.0.11",
@@ -55665,6 +55712,7 @@
         "@overleaf/stream-utils": "*",
         "@overleaf/validation-tools": "*",
         "@phosphor-icons/react": "^2.1.7",
+        "@phosphor-icons/webcomponents": "^2.1.5",
         "@slack/webhook": "^7.0.2",
         "@stripe/react-stripe-js": "^3.9.0",
         "@stripe/stripe-js": "^7.7.0",

+ 17 - 2
services/web/app/views/_mixins/ciam.pug

@@ -2,13 +2,15 @@ include terms_of_service
 include recaptcha
 
 mixin ciamLogo
-	a.brand.overleaf-ds-logo(href='/' aria-label='Overleaf')
+	header.ciam-logo
+		a.brand.overleaf-ds-logo(href='/')
+			span.visually-hidden Overleaf
 
 mixin ciamCardSeparator
 	hr.ciam-card-separator
 
 mixin ciamCardFooter
-	.ciam-card-footer
+	section.ciam-card-footer
 		+ciamCardSeparator
 		.ciam-footer-ds-logo
 			img(
@@ -25,3 +27,16 @@ mixin ciamTermsOfServiceAgreement
 mixin ciamRecaptchaConditions
 	p
 		+recaptchaConditionsContent
+
+mixin ciamCustomFormDangerMessage(key)
+	div(
+		class='notification ciam-notification notification-type-error'
+		hidden
+		data-ol-custom-form-message=key
+		role='alert'
+		aria-live='polite'
+	)
+		.notification-icon
+			ph-warning-circle(aria-hidden='true')
+		.notification-content.text-left
+			block

+ 4 - 0
services/web/frontend/js/features/form-helpers/form-phosphor-icons.ts

@@ -0,0 +1,4 @@
+// These are used in the CIAM registration form
+import '@phosphor-icons/webcomponents/PhBank'
+import '@phosphor-icons/webcomponents/PhEye'
+import '@phosphor-icons/webcomponents/PhEyeSlash'

+ 22 - 4
services/web/frontend/js/features/form-helpers/input-validator.ts

@@ -1,24 +1,42 @@
 import { materialIcon } from '@/features/utils/material-icon'
+import classNames from 'classnames'
+import '@phosphor-icons/webcomponents/PhWarningCircle'
+
+function dsErrorIcon() {
+  const icon = document.createElement('ph-warning-circle')
+  icon.className = 'ciam-form-text-icon'
+  icon.ariaHidden = 'true'
+  return icon
+}
 
 export default function inputValidator(
   inputEl: HTMLInputElement | HTMLTextAreaElement
 ) {
+  const isDsBranded = inputEl.classList.contains('form-control-ds')
   const messageEl = document.createElement('div')
   messageEl.className =
     inputEl.getAttribute('data-ol-validation-message-classes') ||
-    'small text-danger mt-2 form-text'
+    classNames(
+      'small text-danger mt-2 form-text',
+
+      { 'form-text-ds': isDsBranded }
+    )
   messageEl.hidden = true
 
   const messageInnerEl = messageEl.appendChild(document.createElement('span'))
-  messageInnerEl.className = 'form-text-inner'
+  messageInnerEl.className = classNames('form-text-inner', {
+    'form-text-inner-ds': isDsBranded,
+  })
 
   const messageTextNode = document.createTextNode('')
 
-  const iconEl = materialIcon('error')
+  const iconEl = isDsBranded ? dsErrorIcon() : materialIcon('error')
   messageInnerEl.append(iconEl)
   messageInnerEl.append(messageTextNode)
 
-  inputEl.insertAdjacentElement('afterend', messageEl)
+  const inputContainerEl =
+    inputEl.closest('.form-complex-input-container') || inputEl
+  inputContainerEl.insertAdjacentElement('afterend', messageEl)
 
   // Hide messages until the user leaves the input field or submits the form.
   let canDisplayErrorMessages = false

+ 1 - 0
services/web/frontend/js/marketing.ts

@@ -2,6 +2,7 @@ import './utils/webpack-public-path'
 import './infrastructure/error-reporter'
 import './infrastructure/hotjar'
 import './features/form-helpers/hydrate-form'
+import './features/form-helpers/form-phosphor-icons'
 import './features/form-helpers/password-visibility'
 import './features/link-helpers/slow-link'
 import './features/event-tracking'

+ 5 - 7
services/web/frontend/js/shared/components/layouts/ciam-layout.tsx

@@ -1,16 +1,14 @@
 import React, { FC, ReactNode } from 'react'
-import overleafLogo from '@/shared/svgs/overleaf-a-ds-solution-mallard.svg'
 
 type Props = { children: ReactNode }
 
 const CiamLayout: FC<Props> = ({ children }: Props) => (
   <div className="ciam-layout ciam-enabled">
-    <a
-      href="/"
-      aria-label="Overleaf"
-      className="brand"
-      style={{ backgroundImage: `url("${overleafLogo}")` }}
-    />
+    <header className="ciam-logo">
+      <a href="/" className="brand overleaf-ds-logo">
+        <span className="visually-hidden">Overleaf</span>
+      </a>
+    </header>
     <div className="ciam-container">
       <main className="ciam-card" id="main-content">
         {children}

+ 79 - 25
services/web/frontend/stylesheets/ciam/ciam-layout.scss

@@ -1,33 +1,59 @@
+@use 'sass:math';
+
 .overleaf-ds-logo {
   background-image: url('../../../frontend/js/shared/svgs/overleaf-a-ds-solution-mallard.svg');
 }
 
 .ciam-layout {
-  @include full-height-stacked-page;
+  display: flex;
+  flex-direction: column;
+  gap: var(--ds-spacing-400);
+
+  @include media-breakpoint-up(sm) {
+    gap: var(--ds-spacing-800);
+  }
 }
 
 .ciam-enabled {
   @include ds-body-md-regular;
 
-  font-family: var(--ds-font-family-sans), sans-serif;
+  --password-visibility-toggle-width: calc(24px + 2 * var(--ds-spacing-250));
+
+  &,
+  h1 {
+    font-family: var(--ds-font-family-sans), sans-serif;
+    color: var(--ds-color-text-primary);
+  }
 
   .ciam-container {
     flex: 1 1 auto;
-    padding: var(--ds-spacing-350);
+    padding: 0 var(--ds-spacing-300);
   }
 
-  .brand {
+  .ciam-logo {
+    padding: var(--ds-spacing-800) 0 0 0;
+    text-align: center;
+
+    @include media-breakpoint-up(sm) {
+      padding-left: var(--ds-spacing-800);
+      padding-right: var(--ds-spacing-800);
+    }
+  }
+
+  .ciam-logo .brand {
     flex-shrink: 0;
     background-repeat: no-repeat;
     background-position: center center;
     background-size: contain;
-    height: 64px;
-    width: 130px;
-    margin: var(--ds-spacing-350) auto;
+    height: 49px;
+    width: 107px;
+    margin: 0 auto;
     display: block;
 
     @include media-breakpoint-up(sm) {
-      margin: var(--ds-spacing-350) var(--ds-spacing-800);
+      height: 64px;
+      width: 130px;
+      margin: 9px 0; // Vertical margin isn't an exacting spacing value in the design
     }
   }
 
@@ -49,11 +75,32 @@
     padding: var(--ds-spacing-800) var(--ds-spacing-400);
     border-radius: var(--ds-border-radius-400);
     max-width: 464px;
-    margin: var(--ds-spacing-400) auto;
+    margin: 0 auto;
 
     @include media-breakpoint-up(sm) {
       padding: var(--ds-spacing-1300);
     }
+
+    .notification {
+      @include ds-body-sm-regular;
+
+      color: var(--ds-color-text-primary);
+      padding: 0 var(--ds-spacing-400);
+      border-width: 0;
+      border-radius: var(--ds-border-radius-200);
+
+      .notification-icon {
+        font-size: math.div(20em, 14);
+      }
+
+      .notification-content {
+        padding: var(--ds-spacing-400) 0;
+      }
+
+      &.notification-type-error {
+        background-color: var(--ds-color-red-50);
+      }
+    }
   }
 
   .ciam-disclaimers p {
@@ -77,6 +124,7 @@
     p {
       @include ds-body-sm-regular;
 
+      color: var(--ds-color-text-secondary);
       margin-bottom: 0;
     }
   }
@@ -86,25 +134,31 @@
     padding: var(--ds-spacing-200) 0;
   }
 
+  .ciam-stepper {
+    margin: 0;
+    height: 4px;
+    border-radius: var(--ds-border-radius-full);
+
+    .step {
+      background: var(--ds-color-neutral-200);
+    }
+  }
+
   footer {
-    .footer-links {
-      display: flex;
-      gap: var(--ds-spacing-600);
-      justify-content: center;
-      padding: var(--ds-spacing-350) 0;
-      margin: 0 auto var(--spacing-15) auto;
-
-      @include media-breakpoint-up(sm) {
-        margin-left: var(--ds-spacing-800);
-        margin-right: var(--ds-spacing-800);
-        justify-content: start;
-      }
+    display: flex;
+    gap: var(--ds-spacing-600);
+    justify-content: center;
+    padding: var(--ds-spacing-300) 0;
+    margin: 0 auto;
 
-      a {
-        text-decoration: none;
+    @include media-breakpoint-up(sm) {
+      margin-left: var(--ds-spacing-800);
+      margin-right: var(--ds-spacing-800);
+      justify-content: start;
+    }
 
-        @include ds-body-sm-regular;
-      }
+    a {
+      @include ds-body-sm-regular;
     }
   }
 }

+ 42 - 9
services/web/frontend/stylesheets/ciam/ciam-register.scss

@@ -1,13 +1,5 @@
 @import 'ds-design-system';
 
-.ciam-register-container {
-  @include full-viewport-height;
-
-  display: flex;
-  min-height: 100%;
-  flex-direction: column;
-}
-
 .ciam-register-columns {
   display: flex;
   flex-grow: 1;
@@ -15,10 +7,51 @@
 
 .ciam-register-login-link {
   text-align: center;
-  margin: var(--ds-spacing-200) 0;
+  margin: 0;
+  padding: var(--ds-spacing-200) 0;
 }
 
 .ciam-work-uni-sso {
+  color: var(--ds-color-text-secondary);
   padding-top: var(--ds-spacing-200);
   margin-bottom: var(--ds-spacing-400);
+  font-weight: var(--ds-font-weight-semibold);
+}
+
+.ciam-register-container {
+  display: flex;
+  flex-direction: column;
+
+  .login-register-or-text-container {
+    @include ds-body-xs-semibold;
+
+    gap: var(--ds-spacing-250);
+    padding: var(--ds-spacing-200) 0 0 0;
+    margin-bottom: var(--ds-spacing-400);
+
+    &::before,
+    &::after {
+      background-color: var(--ds-color-neutral-200);
+    }
+  }
+
+  .login-register-error-container {
+    padding-bottom: 0;
+
+    .notification {
+      margin-bottom: var(--ds-spacing-400);
+    }
+  }
+}
+
+.ciam-password-group {
+  margin-bottom: var(--ds-spacing-400);
+}
+
+.ciam-password-requirements-message {
+  @include ds-body-sm-regular;
+
+  color: var(--ds-color-text-secondary);
+  padding-top: var(--ds-spacing-200);
+  margin: 0;
 }

+ 7 - 6
services/web/frontend/stylesheets/ciam/ciam-variables.scss

@@ -1,16 +1,17 @@
 // TODO: Replace `fuchsia` by the correct colors.
 
-.ciam-enabled {
+.ciam-enabled,
+.website-redesign:not(.application-page) .ciam-enabled .notification {
   // Links
   // used in services/web/frontend/stylesheets/base/links.scss
-  --link-color: var(--ds-color-text-secondary);
-  --link-hover-color: fuchsia;
+  --link-color: var(--ds-color-text-primary);
+  --link-hover-color: var(--ds-color-text-secondary);
+  --link-visited-color: var(--ds-color-text-secondary);
+  --link-text-decoration: underline;
+  --link-hover-text-decoration: none;
 
   // TODO: validate that this is correct
-  --link-visited-color: var(--ds-color-text-secondary);
   --link-color-dark: fuchsia;
   --link-hover-color-dark: fuchsia;
   --link-visited-color-dark: fuchsia;
-  --link-text-decoration: underline;
-  --link-hover-text-decoration: none;
 }

+ 23 - 3
services/web/frontend/stylesheets/ds/components/form-control.scss

@@ -4,14 +4,26 @@
 .form-control-ds,
 .form-group-ds,
 .form-text-ds,
-.form-label-ds {
+.form-label-ds,
+.form-group-ds label,
+.website-redesign .form-group-ds label,
+.website-redesign .form-label-ds {
+  @include ds-body-sm-semibold;
+
   --bs-body-font-family: var(--ds-font-family-sans), sans-serif;
   --bs-success-rgb: 25, 117, 76; // #19754c
   --bs-danger-rgb: 195, 9, 43; // #c3092b
   --content-placeholder: var(--ds-color-text-disabled);
 
-  // Without this, it inherits the body's --bs-body-font-family which isn't the DS font
-  font-family: var(--ds-font-family-sans), sans-serif;
+  font-family:
+    var(--ds-font-family-sans), sans-serif; // Without this, it inherits the body's --bs-body-font-family which isn't the DS font
+
+  color: var(--ds-color-text-primary);
+  margin-bottom: var(--ds-spacing-100);
+}
+
+.form-group-ds {
+  margin-bottom: var(--ds-spacing-400);
 }
 
 input.form-control.form-control-ds {
@@ -83,3 +95,11 @@ input.form-control.form-control-ds {
     font-size: math.div(20em, 14);
   }
 }
+
+.form-complex-input-container {
+  position: relative;
+}
+
+.ciam-form-input-icon {
+  font-size: var(--ds-font-size-600);
+}

+ 16 - 18
services/web/frontend/stylesheets/pages/login-register.scss

@@ -1,25 +1,23 @@
+:root {
+  --password-visibility-toggle-width: 35px;
+}
+
 .login-register-or-text-container {
+  display: flex;
+  gap: var(--spacing-05);
   padding: var(--spacing-08) 0 var(--spacing-05) 0;
-  margin: 0;
-  line-height: 1;
-  position: relative;
+  align-items: center;
   font-size: var(--font-size-02);
-  text-align: center;
+  line-height: 1;
+  margin: 0;
 
-  &::before {
+  &::before,
+  &::after {
     content: '';
-    position: absolute;
+    display: block;
+    flex-grow: 1;
     height: 1px;
     background-color: var(--neutral-20);
-    left: 0;
-    right: 0;
-    top: calc(var(--spacing-08) + var(--spacing-08) / 4);
-  }
-
-  .login-register-or-text {
-    position: relative;
-    background-color: #fff;
-    padding: 0 var(--spacing-05);
   }
 }
 
@@ -39,7 +37,7 @@
 
   .form-group-password-input {
     input.form-control {
-      padding-right: 35px;
+      padding-right: var(--password-visibility-toggle-width);
     }
   }
 
@@ -47,8 +45,8 @@
     position: absolute;
     right: 0;
     top: 0;
-    width: 35px; // TODO: Should this be calculated ?
-    height: 35px; // TODO: Should this be calculated ?
+    width: var(--password-visibility-toggle-width);
+    height: 100%;
     display: flex;
     align-items: center;
     justify-content: center;

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

@@ -1415,6 +1415,7 @@
   "more_project_collaborators": "<0>More</0> project <0>collaborators</0>",
   "more_than_one_kind_of_snippet_was_requested": "The link to open this content on Overleaf included some invalid parameters. If this keeps happening for links on a particular site, please report this to them.",
   "most_popular_uppercase": "Most popular",
+  "must_be_at_least_n_characters": "Must be at least __n__ characters. Avoid common passwords.",
   "must_be_email_address": "Must be an email address.",
   "must_be_purchased_online": "Must be purchased online",
   "my_library": "My Library",
@@ -2533,8 +2534,10 @@
   "try_to_compile_despite_errors": "Try to compile despite errors",
   "turn_off": "Turn off",
   "turn_off_link_sharing": "Turn off link sharing",
+  "turn_off_password_visibility": "Turn off password visibility",
   "turn_on": "Turn on",
   "turn_on_link_sharing": "Turn on link sharing",
+  "turn_on_password_visibility": "Turn on password visibility",
   "tutorials": "Tutorials",
   "uk": "Ukrainian",
   "unable_to_extract_the_supplied_zip_file": "Opening this content on Overleaf failed because the zip file could not be extracted. Please ensure that it is a valid zip file. If this keeps happening for links on a particular site, please report this to them.",

+ 1 - 0
services/web/package.json

@@ -104,6 +104,7 @@
     "@overleaf/stream-utils": "*",
     "@overleaf/validation-tools": "*",
     "@phosphor-icons/react": "^2.1.7",
+    "@phosphor-icons/webcomponents": "^2.1.5",
     "@slack/webhook": "^7.0.2",
     "@stripe/react-stripe-js": "^3.9.0",
     "@stripe/stripe-js": "^7.7.0",