diff --git a/package-lock.json b/package-lock.json index e7886471dd..7475a76fa8 100644 --- a/package-lock.json +++ b/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", diff --git a/services/web/app/views/_mixins/ciam.pug b/services/web/app/views/_mixins/ciam.pug index 87de19e546..9559f114be 100644 --- a/services/web/app/views/_mixins/ciam.pug +++ b/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 diff --git a/services/web/frontend/js/features/form-helpers/form-phosphor-icons.ts b/services/web/frontend/js/features/form-helpers/form-phosphor-icons.ts new file mode 100644 index 0000000000..7b7e72876f --- /dev/null +++ b/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' diff --git a/services/web/frontend/js/features/form-helpers/input-validator.ts b/services/web/frontend/js/features/form-helpers/input-validator.ts index cb978da05b..1447ee04cf 100644 --- a/services/web/frontend/js/features/form-helpers/input-validator.ts +++ b/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 diff --git a/services/web/frontend/js/marketing.ts b/services/web/frontend/js/marketing.ts index 5fdb663653..dae9b32eee 100644 --- a/services/web/frontend/js/marketing.ts +++ b/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' diff --git a/services/web/frontend/js/shared/components/layouts/ciam-layout.tsx b/services/web/frontend/js/shared/components/layouts/ciam-layout.tsx index 435bc2f400..77c893d787 100644 --- a/services/web/frontend/js/shared/components/layouts/ciam-layout.tsx +++ b/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 = ({ children }: Props) => (
- +
+ + Overleaf + +
{children} diff --git a/services/web/frontend/stylesheets/ciam/ciam-layout.scss b/services/web/frontend/stylesheets/ciam/ciam-layout.scss index 856583d904..72af2e8498 100644 --- a/services/web/frontend/stylesheets/ciam/ciam-layout.scss +++ b/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; + display: flex; + gap: var(--ds-spacing-600); + justify-content: center; + padding: var(--ds-spacing-300) 0; + margin: 0 auto; - @include media-breakpoint-up(sm) { - margin-left: var(--ds-spacing-800); - margin-right: var(--ds-spacing-800); - justify-content: start; - } + @include media-breakpoint-up(sm) { + margin-left: var(--ds-spacing-800); + margin-right: var(--ds-spacing-800); + justify-content: start; + } - a { - text-decoration: none; - - @include ds-body-sm-regular; - } + a { + @include ds-body-sm-regular; } } } diff --git a/services/web/frontend/stylesheets/ciam/ciam-register.scss b/services/web/frontend/stylesheets/ciam/ciam-register.scss index 932acc7fe8..c2fd506db5 100644 --- a/services/web/frontend/stylesheets/ciam/ciam-register.scss +++ b/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; } diff --git a/services/web/frontend/stylesheets/ciam/ciam-variables.scss b/services/web/frontend/stylesheets/ciam/ciam-variables.scss index 032100a4e6..913eb5c5c8 100644 --- a/services/web/frontend/stylesheets/ciam/ciam-variables.scss +++ b/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; } diff --git a/services/web/frontend/stylesheets/ds/components/form-control.scss b/services/web/frontend/stylesheets/ds/components/form-control.scss index 88cc0eaffd..fd06d21d7a 100644 --- a/services/web/frontend/stylesheets/ds/components/form-control.scss +++ b/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); +} diff --git a/services/web/frontend/stylesheets/pages/login-register.scss b/services/web/frontend/stylesheets/pages/login-register.scss index d871d98331..72131cdcb7 100644 --- a/services/web/frontend/stylesheets/pages/login-register.scss +++ b/services/web/frontend/stylesheets/pages/login-register.scss @@ -1,25 +1,23 @@ -.login-register-or-text-container { - padding: var(--spacing-08) 0 var(--spacing-05) 0; - margin: 0; - line-height: 1; - position: relative; - font-size: var(--font-size-02); - text-align: center; +:root { + --password-visibility-toggle-width: 35px; +} - &::before { +.login-register-or-text-container { + display: flex; + gap: var(--spacing-05); + padding: var(--spacing-08) 0 var(--spacing-05) 0; + align-items: center; + font-size: var(--font-size-02); + line-height: 1; + margin: 0; + + &::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; diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 94883969ad..6b235cfc75 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1415,6 +1415,7 @@ "more_project_collaborators": "<0>More project <0>collaborators", "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.", diff --git a/services/web/package.json b/services/web/package.json index d008e4bf49..c351d10658 100644 --- a/services/web/package.json +++ b/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",