Просмотр исходного кода

Migrate subscription related pages to Bootstrap 5 (#26372)

* Enable Bootstrap 5 for the subscription error pages

* Override contact modal on the plans page

* Convert AI Assist related styles to CSS

* Extend single layout-website-redesign.pug template for both Bootstrap 3 and 5

* Formatting to tab indentation

* Add the switcher for AI Assist

* Fix translations in heading

GitOrigin-RevId: 54ddc35602831f1ec1fa83c9f67a091eefda7a77
Rebeka Dekany 1 год назад
Родитель
Сommit
8423829714

+ 0 - 28
services/web/app/views/layout-website-redesign-bootstrap-5.pug

@@ -1,28 +0,0 @@
-extends ./layout-base
-
-include ./_mixins/formMessages
-include ./_mixins/bootstrap_js
-
-block entrypointVar
-	- entrypoint = 'marketing'
-	- isWebsiteRedesign = true
-
-block body
-	if (typeof(suppressNavbar) == "undefined")
-		include layout/navbar-marketing-bootstrap-5
-
-	block content
-
-	if (typeof(suppressFooter) == "undefined")
-		if showThinFooter
-			include layout/thin-footer-bootstrap-5
-		else
-			include layout/fat-footer-website-redesign
-
-	if (typeof(suppressCookieBanner) == 'undefined')
-		include _cookie_banner
-
-	!= moduleIncludes("contactModal-marketing-bootstrap-5", locals)
-
-block prepend foot-scripts
-	+bootstrap-js(5)

+ 15 - 3
services/web/app/views/layout-website-redesign.pug

@@ -8,20 +8,32 @@ block entrypointVar
 
 block body
 	if (typeof(suppressNavbar) == "undefined")
-		include layout/navbar-website-redesign
+		if bootstrapVersion === 5
+			include layout/navbar-marketing-bootstrap-5
+		else
+			include layout/navbar-website-redesign
 
 	block content
 
+	//- bootstrapVersion needed here, because plans.pug uses both BS version
+	//- If the `plans-page-bs5` split test has been completed, remove bootstrapVersion logic
 	if (typeof(suppressFooter) == "undefined")
 		if showThinFooter
-			include layout/thin-footer
+			if bootstrapVersion === 5
+				include layout/thin-footer-bootstrap-5
+			else 
+				include layout/thin-footer
 		else
 			include layout/fat-footer-website-redesign
 
 	if (typeof(suppressCookieBanner) == 'undefined')
 		include _cookie_banner
 
-	!= moduleIncludes("contactModal-marketing", locals)
+	block contactModal
+		if bootstrapVersion === 5
+			!= moduleIncludes("contactModal-marketing-bootstrap-5", locals)
+		else
+			!= moduleIncludes("contactModal-marketing", locals)
 
 block prepend foot-scripts
 	+bootstrap-js(bootstrapVersion)

+ 5 - 1
services/web/app/views/subscriptions/plans/_faq_new.pug

@@ -81,6 +81,10 @@ include ../../_mixins/eyebrow
 	.row
 		.col-xs-12.plans-faq-support
 			span #{translate('still_have_questions')}
-			button(data-ol-open-contact-form-modal="general")
+			button(
+				data-ol-open-contact-form-modal="general"
+				data-bs-toggle=bootstrapVersion === 5 ? "modal" : undefined
+				data-bs-target=bootstrapVersion === 5 ? "#contactUsModal" : undefined
+			)
 				span(style="margin-right: 4px") #{translate('contact_support')}
 				i.icon-md.material-symbols.material-symbols-rounded.material-symbols-arrow-right(aria-hidden="true") arrow_right_alt

+ 2 - 1
services/web/app/views/user/passwordReset-bs5.pug

@@ -1,10 +1,11 @@
-extends ../layout-website-redesign-bootstrap-5
+extends ../layout-website-redesign
 include ../_mixins/recaptcha
 include ../_mixins/notification
 
 block vars
 	- var suppressNavbar = true
 	- var suppressFooter = true
+	- isWebsiteRedesign = true
 
 block content
 	- var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)

+ 5 - 1
services/web/app/views/user/primaryEmailCheck-bs5.pug

@@ -1,4 +1,8 @@
-extends ../layout-website-redesign-bootstrap-5
+extends ../layout-website-redesign
+
+block vars
+	- bootstrap5PageStatus = 'enabled'
+	- isWebsiteRedesign = true
 
 block content
 	main#main-content

+ 65 - 62
services/web/app/views/user/reconfirm-bs5.pug

@@ -1,69 +1,72 @@
-extends ../layout-website-redesign-bootstrap-5
+extends ../layout-website-redesign
 include ../_mixins/recaptcha
 
+block vars
+	- isWebsiteRedesign = true
+
 block content
-    - var email = reconfirm_email ? reconfirm_email : ""
-    - var showCaptcha = settings.recaptcha && settings.recaptcha.siteKey && !(settings.recaptcha.disabled && settings.recaptcha.disabled.passwordReset)
+	- var email = reconfirm_email ? reconfirm_email : ""
+	- 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"
-        )
+	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="")
-        .container.auth-aux-container(style="max-width: 420px;")
-            form(
-                data-ol-async-form
-                name="reconfirmAccountForm"
-                action="/user/reconfirm"
-                method="POST"
-                aria-label=translate('request_reconfirmation_email')
-                captcha=(showCaptcha ? '' : false)
-                captcha-action-name=(showCaptcha ? "passwordReset" : false)
-            )
-                h1.h5.mb-3 #{translate("reconfirm_account")}
-                p #{translate('reconfirm_explained')}
-                    | 
-                    a(href=`mailto:${settings.adminEmail}`) #{settings.adminEmail}
-                    | .
-                
-                div(data-ol-not-sent)
-                    +formMessagesNewStyle()
+	main#main-content(data-ol-captcha-retry-trigger-area="")
+		.container.auth-aux-container(style="max-width: 420px;")
+			form(
+				data-ol-async-form
+				name="reconfirmAccountForm"
+				action="/user/reconfirm"
+				method="POST"
+				aria-label=translate('request_reconfirmation_email')
+				captcha=(showCaptcha ? '' : false)
+				captcha-action-name=(showCaptcha ? "passwordReset" : false)
+			)
+				h1.h5.mb-3 #{translate("reconfirm_account")}
+				p #{translate('reconfirm_explained')}
+					| 
+					a(href=`mailto:${settings.adminEmail}`) #{settings.adminEmail}
+					| .
+				
+				div(data-ol-not-sent)
+					+formMessagesNewStyle()
 
-                    input(type="hidden" name="_csrf" value=csrfToken)
-                    .form-group.mb-3
-                        label.form-label(for='email') #{translate("please_enter_email")}
-                        input.form-control(
-                            aria-label="email"
-                            type='email'
-                            name='email'
-                            placeholder='email@example.com'
-                            required
-                            autofocus
-                            value=email
-                        )
-                    .actions
-                        button.btn.btn-primary.w-100(
-                            style="white-space: normal;"
-                            type='submit'
-                            data-ol-disabled-inflight
-                            aria-label=translate('request_password_reset_to_reconfirm')
-                        )
-                            span(data-ol-inflight="idle")
-                                | #{translate('request_password_reset_to_reconfirm')}
-                            span(hidden data-ol-inflight="pending")
-                                | #{translate('request_password_reset_to_reconfirm')}… 
-                div(hidden data-ol-sent)
-                    div.alert.alert-success(
-                        role="alert"
-                        aria-live="polite"
-                    )
-                        span #{translate('password_reset_email_sent')}
+					input(type="hidden" name="_csrf" value=csrfToken)
+					.form-group.mb-3
+						label.form-label(for='email') #{translate("please_enter_email")}
+						input.form-control(
+							aria-label="email"
+							type='email'
+							name='email'
+							placeholder='email@example.com'
+							required
+							autofocus
+							value=email
+						)
+					.actions
+						button.btn.btn-primary.w-100(
+							style="white-space: normal;"
+							type='submit'
+							data-ol-disabled-inflight
+							aria-label=translate('request_password_reset_to_reconfirm')
+						)
+							span(data-ol-inflight="idle")
+								| #{translate('request_password_reset_to_reconfirm')}
+							span(hidden data-ol-inflight="pending")
+								| #{translate('request_password_reset_to_reconfirm')}… 
+				div(hidden data-ol-sent)
+					div.alert.alert-success(
+						role="alert"
+						aria-live="polite"
+					)
+						span #{translate('password_reset_email_sent')}
 
-            if showCaptcha
-                +recaptchaConditions
+			if showCaptcha
+				+recaptchaConditions

+ 1 - 1
services/web/app/views/user/reconfirm.pug

@@ -23,7 +23,7 @@ block content
 			.row
 				.col-sm-12.col-md-6.col-md-offset-3
 					.card
-						h1.card-header #{translate("reconfirm")} #{translate("Account")}
+						h1.card-header #{translate("reconfirm_account")}
 						p #{translate('reconfirm_explained')} 
 							a(href=`mailto:${settings.adminEmail}`) #{settings.adminEmail}
 							| .

+ 77 - 76
services/web/app/views/user/setPassword-bs5.pug

@@ -1,90 +1,91 @@
-extends ../layout-website-redesign-bootstrap-5
+extends ../layout-website-redesign
 
 block vars
-    - var suppressNavbar = true
-    - var suppressFooter = true
+	- var suppressNavbar = true
+	- var suppressFooter = true
+	- isWebsiteRedesign = true
 
 block content
-    main#main-content
-        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/set"
-                method="POST"
-                data-ol-hide-on-error="token-expired"
-            )
-                div(
-                    hidden
-                    data-ol-sent
-                )
-                    h1.h3.mb-3.mt-0 #{translate("password_updated")}
-                    p.mb-4 #{translate("your_password_has_been_successfully_changed")}.
-                    a.btn.btn-primary.w-100(href='/login') #{translate("log_in_now")}
+	main#main-content
+		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/set"
+				method="POST"
+				data-ol-hide-on-error="token-expired"
+			)
+				div(
+					hidden
+					data-ol-sent
+				)
+					h1.h3.mb-3.mt-0 #{translate("password_updated")}
+					p.mb-4 #{translate("your_password_has_been_successfully_changed")}.
+					a.btn.btn-primary.w-100(href='/login') #{translate("log_in_now")}
 
-                div(data-ol-not-sent)
-                    h1.h3.mb-3.mt-0 #{translate("reset_your_password")}
-                    p(data-ol-hide-on-error-message="token-expired") #{translate("create_a_new_password_for_your_account")}.
-                    +formMessagesNewStyle()
+				div(data-ol-not-sent)
+					h1.h3.mb-3.mt-0 #{translate("reset_your_password")}
+					p(data-ol-hide-on-error-message="token-expired") #{translate("create_a_new_password_for_your_account")}.
+					+formMessagesNewStyle()
 
-                    +customFormMessageNewStyle('password-contains-email', 'danger')
-                        | #{translate('invalid_password_contains_email')}.
-                        | #{translate('use_a_different_password')}.
+					+customFormMessageNewStyle('password-contains-email', 'danger')
+						| #{translate('invalid_password_contains_email')}.
+						| #{translate('use_a_different_password')}.
 
-                    +customFormMessageNewStyle('password-too-similar', 'danger')
-                        | #{translate('invalid_password_too_similar')}.
-                        | #{translate('use_a_different_password')}.
+					+customFormMessageNewStyle('password-too-similar', 'danger')
+						| #{translate('invalid_password_too_similar')}.
+						| #{translate('use_a_different_password')}.
 
-                    +customFormMessageNewStyle('token-expired', 'danger')
-                        | #{translate('password_reset_token_expired')}
-                        br
-                        a(href="/user/password/reset")
-                            | #{translate('request_new_password_reset_email')}
+					+customFormMessageNewStyle('token-expired', 'danger')
+						| #{translate('password_reset_token_expired')}
+						br
+						a(href="/user/password/reset")
+							| #{translate('request_new_password_reset_email')}
 
-                    input(type="hidden" name="_csrf" value=csrfToken)
-                    input(type="text" hidden name="email" autocomplete="username" value=email)
+					input(type="hidden" name="_csrf" value=csrfToken)
+					input(type="text" hidden name="email" autocomplete="username" value=email)
 
-                    .form-group.mb-3
-                        label.form-label(for='passwordField', data-ol-hide-on-error-message="token-expired") #{translate("new_password")}
-                        input.form-control.auth-aux-new-password#passwordField(
-                            type='password'
-                            name='password'
-                            autocomplete="new-password"
-                            autofocus
-                            required
-                            minlength=settings.passwordStrengthOptions.length.min
-                        )
+					.form-group.mb-3
+						label.form-label(for='passwordField', data-ol-hide-on-error-message="token-expired") #{translate("new_password")}
+						input.form-control.auth-aux-new-password#passwordField(
+							type='password'
+							name='password'
+							autocomplete="new-password"
+							autofocus
+							required
+							minlength=settings.passwordStrengthOptions.length.min
+						)
 
-                        +customValidationMessageNewStyle('invalid-password')
-                            | #{translate('invalid_password')}.
+						+customValidationMessageNewStyle('invalid-password')
+							| #{translate('invalid_password')}.
 
-                        +customValidationMessageNewStyle('password-must-be-different')
-                            | #{translate('password_cant_be_the_same_as_current_one')}.
+						+customValidationMessageNewStyle('password-must-be-different')
+							| #{translate('password_cant_be_the_same_as_current_one')}.
 
-                        +customValidationMessageNewStyle('password-must-be-strong')
-                            | !{translate('password_was_detected_on_a_public_list_of_known_compromised_passwords', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}])}.
-                            | #{translate('use_a_different_password')}.
+						+customValidationMessageNewStyle('password-must-be-strong')
+							| !{translate('password_was_detected_on_a_public_list_of_known_compromised_passwords', {}, [{name: 'a', attrs: {href: 'https://haveibeenpwned.com/passwords', rel: 'noopener noreferrer', target: '_blank'}}])}.
+							| #{translate('use_a_different_password')}.
 
-                        input(
-                            type="hidden"
-                            name="passwordResetToken"
-                            value=passwordResetToken
-                        )
-                    div(data-ol-hide-on-error-message="token-expired")
-                        div #{translate('in_order_to_have_a_secure_account_make_sure_your_password')}
-                        ul.mb-3.ps-4
-                            li #{translate('is_longer_than_n_characters', {n: settings.passwordStrengthOptions.length.min})}
-                            li #{translate('does_not_contain_or_significantly_match_your_email')}
-                            li #{translate('is_not_used_on_any_other_website')}
-                    .actions
-                        button.btn.btn-primary.w-100(
-                            type='submit'
-                            data-ol-disabled-inflight
-                            aria-label=translate('set_new_password')
-                        ) 
-                            span(data-ol-inflight="idle")
-                                | #{translate('set_new_password')}
-                            span(hidden data-ol-inflight="pending")
-                                | #{translate('set_new_password')}…
+						input(
+							type="hidden"
+							name="passwordResetToken"
+							value=passwordResetToken
+						)
+					div(data-ol-hide-on-error-message="token-expired")
+						div #{translate('in_order_to_have_a_secure_account_make_sure_your_password')}
+						ul.mb-3.ps-4
+							li #{translate('is_longer_than_n_characters', {n: settings.passwordStrengthOptions.length.min})}
+							li #{translate('does_not_contain_or_significantly_match_your_email')}
+							li #{translate('is_not_used_on_any_other_website')}
+					.actions
+						button.btn.btn-primary.w-100(
+							type='submit'
+							data-ol-disabled-inflight
+							aria-label=translate('set_new_password')
+						) 
+							span(data-ol-inflight="idle")
+								| #{translate('set_new_password')}
+							span(hidden data-ol-inflight="pending")
+								| #{translate('set_new_password')}…

+ 241 - 0
services/web/frontend/stylesheets/bootstrap-5/pages/plans.scss

@@ -767,6 +767,247 @@ $z-index-group-member-picker-list: 1;
     border-left: var(--border-width-base) solid var(--green-50);
   }
 
+  .ai-assist {
+    color: var(--neutral-90);
+
+    @include media-breakpoint-up(lg) {
+      padding-left: 0 !important;
+      padding-right: 0 !important;
+    }
+
+    .ai-assist-heading {
+      margin-top: var(--spacing-12);
+      margin-bottom: var(--spacing-12);
+      text-align: center;
+    }
+
+    .ai-assist-container {
+      padding: var(--spacing-10);
+      border: var(--spacing-02) solid var(--neutral-10);
+      border-radius: var(--border-radius-large-new);
+      background:
+        linear-gradient(270deg, var(--blue-10), #fff) 0.13%,
+        #fff 63.21%;
+
+      @include media-breakpoint-down(lg) {
+        background: var(--bg-light-primary);
+        border-width: 2px;
+        border-radius: var(--border-radius-medium);
+        border-color: var(--premium-gradient);
+        padding: var(--spacing-09);
+      }
+
+      .ai-assist-content {
+        display: flex;
+        justify-content: space-between;
+        gap: var(--spacing-07);
+
+        @include media-breakpoint-down(lg) {
+          flex-direction: column;
+        }
+
+        .ai-assist-left {
+          display: flex;
+          gap: var(--spacing-07);
+          flex-direction: column;
+          max-width: 480px;
+
+          @include media-breakpoint-down(lg) {
+            max-width: 100%;
+          }
+
+          .ai-assist-price {
+            font-weight: 600;
+            font-size: var(--font-size-03);
+
+            @include media-breakpoint-down(lg) {
+              font-weight: normal;
+              font-size: var(--font-size-02);
+
+              .price {
+                font-size: var(--font-size-08);
+                font-weight: 600;
+              }
+            }
+          }
+
+          .ai-assist-annually-price {
+            display: block;
+          }
+
+          .ai-assist-monthly-price {
+            display: none;
+          }
+
+          .ai-assist-header-switch {
+            display: none;
+          }
+
+          .ai-assist-header {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+
+            .ai-assist-header-content {
+              display: flex;
+              gap: var(--spacing-06);
+              align-items: center;
+
+              h3 {
+                font-size: var(--font-size-07);
+                margin: 0;
+                padding: 0;
+
+                @include media-breakpoint-down(lg) {
+                  font-size: var(--font-size-05);
+                }
+              }
+
+              img {
+                @include media-breakpoint-down(lg) {
+                  width: 24px;
+                }
+              }
+            }
+
+            .ai-assist-header-switch {
+              align-items: center;
+              gap: var(--spacing-03);
+              user-select: none;
+
+              input {
+                margin: 0;
+              }
+
+              label {
+                margin: 0;
+                font-weight: normal;
+                font-size: var(--font-size-03);
+              }
+            }
+          }
+        }
+
+        .ai-assist-right {
+          display: flex;
+          align-items: center;
+          gap: var(--spacing-06);
+
+          .ai-assist-features {
+            list-style: none;
+            display: flex;
+            flex-direction: column;
+            gap: var(--spacing-04);
+            min-width: 210px;
+            margin-bottom: 0;
+
+            .feature-item {
+              display: flex;
+              align-items: center;
+              font-size: var(--font-size-03);
+              cursor: default;
+
+              .feature-icon {
+                margin-right: var(--spacing-05);
+                color: var(--neutral-70);
+                width: 16px;
+                height: 16px;
+              }
+
+              .feature-text {
+                color: var(--neutral-90);
+              }
+            }
+
+            @include media-breakpoint-up(lg) {
+              .feature-item-selected {
+                .feature-text {
+                  color: var(--blue-50);
+                  font-weight: 600;
+                }
+
+                .feature-icon {
+                  content: url(../../../../public/img/check-circle-blue-filled.svg);
+                }
+              }
+            }
+          }
+
+          .ai-assist-images {
+            display: flex;
+            align-items: flex-start;
+            width: 280px;
+            justify-content: center;
+
+            @include media-breakpoint-down(lg) {
+              display: none;
+            }
+          }
+        }
+      }
+    }
+
+    .ai-assist-header-switch {
+      display: flex !important;
+    }
+
+    .ai-assist-button-container {
+      display: flex;
+      gap: var(--spacing-05);
+
+      &.mobile {
+        display: none;
+        margin-top: var(--spacing-06);
+        flex-direction: column;
+      }
+
+      @include media-breakpoint-down(lg) {
+        &.mobile {
+          display: flex;
+        }
+
+        &.desktop {
+          display: none;
+        }
+      }
+
+      .btn.monthly {
+        display: none;
+      }
+    }
+
+    .overleaf-assist-screenshot {
+      height: auto;
+      max-width: 280px;
+      max-height: 200px;
+      border-radius: var(--border-radius-base-new);
+      display: none;
+
+      &.selected {
+        display: block;
+        animation: fade-in 1s;
+      }
+    }
+  }
+
+  .ai-assist.monthly-period {
+    .ai-assist-annually-price {
+      display: none !important;
+    }
+
+    .ai-assist-monthly-price {
+      display: block !important;
+    }
+
+    .ai-assist-button-container .btn.annual {
+      display: none !important;
+    }
+
+    .ai-assist-button-container .btn.monthly {
+      display: block !important;
+    }
+  }
+
   .plans-new-organizations {
     padding: var(--spacing-13) var(--spacing-08);
 

+ 4 - 1
services/web/modules/user-activate/app/views/user/activate.pug

@@ -1,4 +1,7 @@
-extends ../../../../../app/views/layout-website-redesign-bootstrap-5
+extends ../../../../../app/views/layout-website-redesign
+
+block vars
+	- isWebsiteRedesign = true
 
 block content
 	main.content.content-alt#main-content