Parcourir la source

[web] Create a first implementation of the CIAM version of the email confirmation page (#29432)

* Move `main#main-content.content.content-alt` into a React component (`ContentLayout`)

* Create the CIAM variant of `RegistrationConfirmEmailForm`

* `bin/run web npm run extract-translations`

* Use `CiamLayout` in the Storybook demo

* Add `SplitTestProvider` in tests

* Fix Storybook: Wrap `RegistrationConfirmEmailForm` Story in `onboarding-confirm-email`

* Refactor SCSS files:

- only imports in all.scss
- split .storybook-layout and .storybook-enabled
- extract ciam-spacing.scss

GitOrigin-RevId: f4a214a0978423a1621dd8f60bf459af7b8f877e
Antoine Clausse il y a 9 mois
Parent
commit
2ebc411db4

+ 5 - 0
services/web/.storybook/utils/with-split-tests.tsx

@@ -12,6 +12,11 @@ export const defaultSplitTestsArgTypes = {
     },
     options: ['enabled'],
   },
+  uniaccessphase1: {
+    description: 'Enable CIAM designs',
+    control: { type: 'select' as const },
+    options: ['default', 'enabled'],
+  },
 }
 
 export const withSplitTests = <ArgTypes = typeof defaultSplitTestsArgTypes,>(

+ 2 - 0
services/web/frontend/extracted-translations.json

@@ -2086,6 +2086,7 @@
   "us_gov_banner_government_purchasing": "",
   "us_gov_banner_small_business_reseller": "",
   "usage_metrics": "",
+  "use_a_different_email": "",
   "use_a_different_password": "",
   "use_saml_metadata_to_configure_sso_with_idp": "",
   "use_your_own_machine": "",
@@ -2114,6 +2115,7 @@
   "vat": "",
   "vat_number": "",
   "verify_email_address_before_enabling_managed_users": "",
+  "verify_your_email_address": "",
   "view": "",
   "view_all": "",
   "view_audit_logs_group_subtext": "",

+ 34 - 17
services/web/frontend/js/features/settings/components/emails/confirm-email-form.tsx

@@ -2,7 +2,7 @@ import { postJSON } from '@/infrastructure/fetch-json'
 import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n'
 import Notification from '@/shared/components/notification'
 import getMeta from '@/utils/meta'
-import { FormEvent, MouseEventHandler, ReactNode, useState } from 'react'
+import { FormEvent, MouseEventHandler, useState } from 'react'
 import { Trans, useTranslation } from 'react-i18next'
 import LoadingSpinner from '@/shared/components/loading-spinner'
 import MaterialIcon from '@/shared/components/material-icon'
@@ -27,8 +27,9 @@ type ConfirmEmailFormProps = {
   onSuccessfulConfirmation?: () => void
   interstitial: boolean
   isModal?: boolean
-  onCancel?: () => void
+  onCancel?: MouseEventHandler<HTMLButtonElement>
   outerError?: string
+  isCiam?: boolean
 }
 
 export function ConfirmEmailForm({
@@ -43,6 +44,7 @@ export function ConfirmEmailForm({
   isModal,
   onCancel,
   outerError,
+  isCiam,
 }: ConfirmEmailFormProps) {
   const { t } = useTranslation()
   const [confirmationCode, setConfirmationCode] = useState('')
@@ -163,20 +165,6 @@ export function ConfirmEmailForm({
     )
   }
 
-  let intro: ReactNode | null = (
-    <h5 className="h5">{t('confirm_your_email')}</h5>
-  )
-  if (isModal)
-    intro = outerErrorDisplay ? (
-      <div className="mt-4" />
-    ) : (
-      <h3 className="h5">{outerErrorDisplay ? null : t('we_sent_code')}</h3>
-    )
-  if (interstitial)
-    intro = (
-      <h1 className="h3 interstitial-header">{t('confirm_your_email')}</h1>
-    )
-
   return (
     <form
       onSubmit={submitHandler}
@@ -196,7 +184,12 @@ export function ConfirmEmailForm({
           />
         )}
 
-        {intro}
+        <Title
+          isModal={isModal}
+          interstitial={interstitial}
+          isCiam={isCiam}
+          outerErrorDisplay={outerErrorDisplay}
+        />
 
         <OLFormLabel htmlFor="one-time-code">
           {isModal
@@ -260,6 +253,30 @@ export function ConfirmEmailForm({
   )
 }
 
+function Title({
+  isModal,
+  interstitial,
+  outerErrorDisplay,
+  isCiam,
+}: {
+  isModal?: boolean
+  interstitial: boolean
+  isCiam?: boolean
+  outerErrorDisplay: string | null
+}) {
+  const { t } = useTranslation()
+  if (isCiam) return <h1>{t('verify_your_email_address')}</h1>
+  if (isModal)
+    return outerErrorDisplay ? (
+      <div className="mt-4" />
+    ) : (
+      <h3 className="h5">{outerErrorDisplay ? null : t('we_sent_code')}</h3>
+    )
+  if (interstitial)
+    return <h1 className="h3 interstitial-header">{t('confirm_your_email')}</h1>
+  return <h5 className="h5">{t('confirm_your_email')}</h5>
+}
+
 function ConfirmEmailSuccessfullForm({
   successMessage,
   successButtonText,

+ 26 - 0
services/web/frontend/js/shared/components/layouts/ciam-layout.tsx

@@ -0,0 +1,26 @@
+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}")` }}
+    />
+    <div className="ciam-container">
+      <main className="ciam-card" id="main-content">
+        {children}
+      </main>
+    </div>
+    <footer>
+      <a href="https://www.overleaf.com/legal#Privacy">Privacy</a>
+      <a href="https://www.overleaf.com/legal#Terms">Terms</a>
+    </footer>
+  </div>
+)
+
+export default CiamLayout

+ 16 - 0
services/web/frontend/js/shared/components/layouts/content-layout.tsx

@@ -0,0 +1,16 @@
+import React, { FC, ReactNode } from 'react'
+
+type Props = { children: ReactNode; isMain?: boolean; alt?: boolean }
+
+const ContentLayout: FC<Props> = ({ children, isMain, alt }: Props) => {
+  const className = alt ? 'content content-alt' : 'content'
+  return isMain ? (
+    <main className={className} id="main-content">
+      {children}
+    </main>
+  ) : (
+    <div className={className}>{children}</div>
+  )
+}
+
+export default ContentLayout

+ 8 - 22
services/web/frontend/stories/page-layouts.stories.tsx

@@ -5,7 +5,7 @@ import OLPageContentCard from '@/shared/components/ol/ol-page-content-card'
 import OLRow from '@/shared/components/ol/ol-row'
 import OLCol from '@/shared/components/ol/ol-col'
 import OLButton from '@/shared/components/ol/ol-button'
-import overleafLogo from '@/shared/svgs/overleaf-a-ds-solution-mallard.svg'
+import CiamLayout from '@/shared/components/layouts/ciam-layout'
 
 const lorem = (n: number) => {
   const quacks = ['quack', 'quack', 'quack', 'quak']
@@ -249,27 +249,13 @@ export const CompleteRegistration = () => (
 )
 
 export const Ciam = () => (
-  <div className="ciam-layout">
-    <a
-      href="/"
-      aria-label="Overleaf"
-      className="brand"
-      style={{ backgroundImage: `url("${overleafLogo}")` }}
-    />
-    <div className="ciam-container">
-      <main className="ciam-card" id="main-content">
-        <h1>Create your Overleaf account</h1>
-        <p>{lorem(20)}</p>
-        <hr />
-        <p>{lorem(20)}</p>
-        <OLButton>Button</OLButton>
-      </main>
-    </div>
-    <footer>
-      <a href="https://www.overleaf.com/legal#Privacy">Privacy</a>
-      <a href="https://www.overleaf.com/legal#Terms">Terms</a>
-    </footer>
-  </div>
+  <CiamLayout>
+    <h1>Create your Overleaf account</h1>
+    <p>{lorem(20)}</p>
+    <hr />
+    <p>{lorem(20)}</p>
+    <OLButton>Button</OLButton>
+  </CiamLayout>
 )
 
 export default {

+ 4 - 69
services/web/frontend/stylesheets/ciam/all.scss

@@ -1,70 +1,5 @@
-@import 'ciam-variables';
+@import 'ciam-colors';
+@import 'ciam-layout';
 @import 'ciam-mixins';
-
-.ciam-layout {
-  padding: var(--ciam-spacing-350);
-  display: flex;
-  min-height: 100%;
-  flex-direction: column;
-  font-family: var(--ciam-font-family-sans), sans-serif;
-  color: var(--ciam-color-text-primary);
-  font-size: var(--ciam-font-size-400);
-  line-height: 1.5;
-
-  @include ciam-body-md-regular;
-
-  .ciam-container {
-    flex: 1 1 auto;
-  }
-
-  .brand {
-    background-repeat: no-repeat;
-    background-position: center center;
-    background-size: contain;
-    height: 64px;
-    width: 130px;
-    margin: var(--ciam-spacing-350) auto;
-    display: block;
-
-    @include media-breakpoint-up(sm) {
-      margin: var(--ciam-spacing-350) var(--ciam-spacing-800);
-    }
-  }
-
-  h1 {
-    @include ciam-heading-sm-semibold;
-  }
-
-  .ciam-card {
-    box-shadow:
-      0 4px 6px -4px rgb(0 0 0 / 10%),
-      0 1px 29px -3px rgb(0 0 0 / 16%);
-    padding: var(--ciam-spacing-800) var(--ciam-spacing-400);
-    border-radius: var(--ciam-border-radius-400);
-    max-width: 460px;
-    margin: var(--ciam-spacing-400) auto;
-
-    @include media-breakpoint-up(sm) {
-      padding: var(--ciam-spacing-1300);
-    }
-  }
-
-  footer {
-    display: flex;
-    gap: var(--ciam-spacing-600);
-    text-transform: uppercase;
-    justify-content: center;
-    margin: var(--ciam-spacing-350) auto;
-
-    @include media-breakpoint-up(sm) {
-      margin: var(--ciam-spacing-350) var(--ciam-spacing-800);
-      justify-content: start;
-    }
-
-    a {
-      text-decoration: none;
-
-      @include ciam-body-sm-regular;
-    }
-  }
-}
+@import 'ciam-spacing';
+@import 'ciam-variables';

+ 1 - 1
services/web/frontend/stylesheets/ciam/ciam-colors.scss

@@ -1,4 +1,4 @@
-.ciam-layout {
+.ciam-enabled {
   --ciam-color-neutral-50: #fafafa;
   --ciam-color-neutral-100: #f2f2f2;
   --ciam-color-neutral-200: #e6e6e6;

+ 72 - 0
services/web/frontend/stylesheets/ciam/ciam-layout.scss

@@ -0,0 +1,72 @@
+@import 'ciam-mixins';
+
+.ciam-layout {
+  padding: var(--ciam-spacing-350);
+  display: flex;
+  min-height: 100vh;
+  flex-direction: column;
+
+  @include ciam-body-md-regular;
+}
+
+.ciam-enabled {
+  font-family: var(--ciam-font-family-sans), sans-serif;
+  color: var(--ciam-color-text-primary);
+  font-size: var(--ciam-font-size-400);
+  line-height: 1.5;
+
+  .ciam-container {
+    flex: 1 1 auto;
+  }
+
+  .brand {
+    background-repeat: no-repeat;
+    background-position: center center;
+    background-size: contain;
+    height: 64px;
+    width: 130px;
+    margin: var(--ciam-spacing-350) auto;
+    display: block;
+
+    @include media-breakpoint-up(sm) {
+      margin: var(--ciam-spacing-350) var(--ciam-spacing-800);
+    }
+  }
+
+  h1 {
+    @include ciam-heading-sm-semibold;
+  }
+
+  .ciam-card {
+    box-shadow:
+      0 4px 6px -4px rgb(0 0 0 / 10%),
+      0 1px 29px -3px rgb(0 0 0 / 16%);
+    padding: var(--ciam-spacing-800) var(--ciam-spacing-400);
+    border-radius: var(--ciam-border-radius-400);
+    max-width: 460px;
+    margin: var(--ciam-spacing-400) auto;
+
+    @include media-breakpoint-up(sm) {
+      padding: var(--ciam-spacing-1300);
+    }
+  }
+
+  footer {
+    display: flex;
+    gap: var(--ciam-spacing-600);
+    text-transform: uppercase;
+    justify-content: center;
+    margin: var(--ciam-spacing-350) auto;
+
+    @include media-breakpoint-up(sm) {
+      margin: var(--ciam-spacing-350) var(--ciam-spacing-800);
+      justify-content: start;
+    }
+
+    a {
+      text-decoration: none;
+
+      @include ciam-body-sm-regular;
+    }
+  }
+}

+ 66 - 0
services/web/frontend/stylesheets/ciam/ciam-spacing.scss

@@ -0,0 +1,66 @@
+.ciam-enabled {
+  --ciam-font-weight-regular: 400;
+  --ciam-font-weight-medium: 500;
+  --ciam-font-weight-semibold: 600;
+  --ciam-font-weight-bold: 700;
+  --ciam-font-family-sans: inter, sans-serif;
+  --ciam-spacing-50: 2px;
+  --ciam-spacing-100: 4px;
+  --ciam-spacing-150: 6px;
+  --ciam-spacing-200: 8px;
+  --ciam-spacing-250: 10px;
+  --ciam-spacing-300: 12px;
+  --ciam-spacing-350: 14px;
+  --ciam-spacing-400: 16px;
+  --ciam-spacing-500: 20px;
+  --ciam-spacing-600: 24px;
+  --ciam-spacing-700: 28px;
+  --ciam-spacing-800: 32px;
+  --ciam-spacing-900: 36px;
+  --ciam-spacing-1000: 40px;
+  --ciam-spacing-1100: 44px;
+  --ciam-spacing-1200: 48px;
+  --ciam-spacing-1300: 52px;
+  --ciam-spacing-1400: 56px;
+  --ciam-spacing-1500: 60px;
+  --ciam-spacing-1600: 64px;
+  --ciam-spacing-1700: 68px;
+  --ciam-spacing-1800: 72px;
+  --ciam-spacing-1900: 76px;
+  --ciam-spacing-2000: 80px;
+  --ciam-spacing-2100: 84px;
+  --ciam-spacing-2200: 88px;
+  --ciam-spacing-2300: 92px;
+  --ciam-spacing-2400: 96px;
+  --ciam-base-unit: 4px;
+  --ciam-font-size-300: 12px;
+  --ciam-font-size-350: 14px;
+  --ciam-font-size-400: 16px;
+  --ciam-font-size-450: 18px;
+  --ciam-font-size-500: 20px;
+  --ciam-font-size-600: 24px;
+  --ciam-font-size-700: 28px;
+  --ciam-font-size-800: 32px;
+  --ciam-font-size-1000: 40px;
+  --ciam-font-size-1400: 56px;
+  --ciam-font-size-1800: 72px;
+  --ciam-font-line-height-400: 16px;
+  --ciam-font-line-height-500: 20px;
+  --ciam-font-line-height-600: 24px;
+  --ciam-font-line-height-700: 28px;
+  --ciam-font-line-height-800: 32px;
+  --ciam-font-line-height-900: 36px;
+  --ciam-font-line-height-1000: 40px;
+  --ciam-font-line-height-1200: 48px;
+  --ciam-font-line-height-1600: 64px;
+  --ciam-font-line-height-1800: 72px;
+  --ciam-border-width-25: 1px;
+  --ciam-border-width-50: 2px;
+  --ciam-border-radius-50: 2px;
+  --ciam-border-radius-100: 4px;
+  --ciam-border-radius-200: 8px;
+  --ciam-border-radius-300: 12px;
+  --ciam-border-radius-400: 16px;
+  --ciam-border-radius-600: 24px;
+  --ciam-border-radius-full: 9999px;
+}

+ 1 - 16
services/web/frontend/stylesheets/ciam/ciam-variables.scss

@@ -1,24 +1,9 @@
-@import 'ciam-mixins';
-@import 'ciam-colors';
-
 // TODO: Replace `fuchsia` by the correct colors.
 
-.ciam-layout {
-  // Spacings
-  --ciam-spacing-200: 8px;
-  --ciam-spacing-250: 10px;
-  --ciam-spacing-350: 12px;
-  --ciam-spacing-400: 16px;
-  --ciam-spacing-600: 24px; // TODO: confirm this variable name (couldn't find in design system)
-  --ciam-spacing-800: 32px; // TODO: confirm this variable name (couldn't find in design system)
-  --ciam-spacing-1300: 52px;
-
+.ciam-enabled {
   // Base variables
   --ciam-color-text-secondary: var(--ciam-color-neutral-800);
   --ciam-color-text-primary: var(--ciam-color-neutral-900);
-  --ciam-border-radius-200: var(--ciam-spacing-300);
-  --ciam-border-radius-400: var(--ciam-spacing-400);
-  --ciam-font-family-sans: 'Inter', sans-serif;
 
   // Links
   // used in services/web/frontend/stylesheets/base/links.scss

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

@@ -2617,6 +2617,7 @@
   "us_gov_banner_government_purchasing": "<0>Get __appName__ for US federal government. </0>Move faster through procurement with our tailored purchasing options. Talk to our government team.",
   "us_gov_banner_small_business_reseller": "<0>Easy procurement for US federal government. </0>We partner with small business resellers to help you buy Overleaf organizational plans. Talk to our government team.",
   "usage_metrics": "Usage metrics",
+  "use_a_different_email": "Use a <0>different email</0>.",
   "use_a_different_password": "Please use a different password",
   "use_saml_metadata_to_configure_sso_with_idp": "Use the Overleaf SAML metadata to configure SSO with your Identity Provider.",
   "use_your_own_machine": "Use your own machine, with your own setup",
@@ -2652,6 +2653,7 @@
   "vat": "VAT",
   "vat_number": "VAT Number",
   "verify_email_address_before_enabling_managed_users": "You need to verify your email address before enabling managed users.",
+  "verify_your_email_address": "Verify your email address",
   "view": "View",
   "view_all": "View all",
   "view_audit_logs_group_subtext": "View and download audit logs for your group subscription",