mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
Merge pull request #29654 from overleaf/ac-ciam-confirm-email-storybook
[web] CIAM design for Email confirmation form GitOrigin-RevId: 3e66c45fe20073eb0600b8243761dbe82d7dc6b2
This commit is contained in:
@@ -973,6 +973,7 @@
|
||||
"let_us_know_how_we_can_help": "",
|
||||
"let_us_know_what_you_think": "",
|
||||
"lets_get_those_premium_features": "",
|
||||
"lets_get_you_set_up": "",
|
||||
"library": "",
|
||||
"licenses": "",
|
||||
"limited_document_history": "",
|
||||
@@ -2109,6 +2110,7 @@
|
||||
"value_must_be_at_least_x": "",
|
||||
"vat": "",
|
||||
"vat_number": "",
|
||||
"verification_code": "",
|
||||
"verify_email_address_before_enabling_managed_users": "",
|
||||
"verify_your_email_address": "",
|
||||
"view": "",
|
||||
@@ -2250,6 +2252,7 @@
|
||||
"your_current_plan_gives_you": "",
|
||||
"your_current_plan_supports_up_to_x_licenses": "",
|
||||
"your_current_project_will_revert_to_the_version_from_time": "",
|
||||
"your_email_is_confirmed": "",
|
||||
"your_feedback_matters_answer_two_quick_questions": "",
|
||||
"your_git_access_info": "",
|
||||
"your_git_access_info_bullet_1": "",
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { forwardRef } from 'react'
|
||||
import { Form, FormControlProps } from 'react-bootstrap'
|
||||
import classnames from 'classnames'
|
||||
|
||||
interface CIAMSixDigitsInputProps extends FormControlProps {
|
||||
value: string | undefined
|
||||
}
|
||||
|
||||
const separator = '\u2007' // figure space
|
||||
|
||||
const CIAMSixDigitsInput = forwardRef<
|
||||
HTMLInputElement,
|
||||
CIAMSixDigitsInputProps
|
||||
>(({ className, onChange, value, ...props }, ref) => {
|
||||
const group1 = value?.slice(0, 3) || ''
|
||||
const group2 = value?.slice(3, 6) || ''
|
||||
const displayValue = group2 ? `${group1}${separator}${group2}` : group1
|
||||
return (
|
||||
<div className="ciam-six-digits-container">
|
||||
<Form.Control
|
||||
ref={ref}
|
||||
{...props}
|
||||
size="lg"
|
||||
onChange={v => {
|
||||
const inputValue = v.target.value
|
||||
const sanitizedValue = inputValue.replaceAll(/\D/g, '').slice(0, 6)
|
||||
onChange?.({
|
||||
...v,
|
||||
target: { ...v.target, value: sanitizedValue },
|
||||
currentTarget: { ...v.currentTarget, value: sanitizedValue },
|
||||
})
|
||||
}}
|
||||
value={displayValue}
|
||||
className={classnames(
|
||||
'form-control-ds ciam-six-digits-input',
|
||||
className
|
||||
)}
|
||||
maxLength={7}
|
||||
inputMode="numeric"
|
||||
/>
|
||||
<span className="ciam-six-digits-dash" aria-hidden>
|
||||
-
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
CIAMSixDigitsInput.displayName = 'CIAMSixDigitsInput'
|
||||
|
||||
export default CIAMSixDigitsInput
|
||||
@@ -2,14 +2,25 @@ 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, useState } from 'react'
|
||||
import {
|
||||
ChangeEventHandler,
|
||||
ComponentProps,
|
||||
FormEvent,
|
||||
MouseEventHandler,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import LoadingSpinner from '@/shared/components/loading-spinner'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import OLFormLabel from '@/shared/components/ol/ol-form-label'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { useLocation } from '@/shared/hooks/use-location'
|
||||
import DSFormLabel from '@/shared/components/ds/ds-form-label'
|
||||
import DSButton from '@/shared/components/ds/ds-button'
|
||||
import CIAMSixDigitsInput from '@/features/settings/components/emails/ciam-six-digits-input'
|
||||
import OLFormText from '@/shared/components/ol/ol-form-text'
|
||||
import DSFormText from '@/shared/components/ds/ds-form-text'
|
||||
|
||||
type Feedback = {
|
||||
type: 'input' | 'alert'
|
||||
@@ -19,7 +30,7 @@ type Feedback = {
|
||||
|
||||
type ConfirmEmailFormProps = {
|
||||
confirmationEndpoint: string
|
||||
flow: string
|
||||
flow: 'registration' | 'resend' | 'secondary'
|
||||
resendEndpoint: string
|
||||
successMessage?: React.ReactNode
|
||||
successButtonText?: string
|
||||
@@ -32,6 +43,15 @@ type ConfirmEmailFormProps = {
|
||||
isCiam?: boolean
|
||||
}
|
||||
|
||||
const OLSixDigitsInput = (props: ComponentProps<'input'>) => (
|
||||
<input
|
||||
inputMode="numeric"
|
||||
maxLength={6}
|
||||
className="form-control"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
export function ConfirmEmailForm({
|
||||
confirmationEndpoint,
|
||||
flow,
|
||||
@@ -146,7 +166,7 @@ export function ConfirmEmailForm({
|
||||
})
|
||||
}
|
||||
|
||||
const changeHandler = (e: FormEvent<HTMLInputElement>) => {
|
||||
const changeHandler: ChangeEventHandler<HTMLInputElement> = e => {
|
||||
setConfirmationCode(e.currentTarget.value)
|
||||
setFeedback(null)
|
||||
}
|
||||
@@ -161,10 +181,21 @@ export function ConfirmEmailForm({
|
||||
successMessage={successMessage}
|
||||
successButtonText={successButtonText}
|
||||
redirectTo={successRedirectPath}
|
||||
autoRedirect={isCiam ? 8000 : false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const longLabel = isModal
|
||||
? t('enter_the_code', { email })
|
||||
: t('enter_the_confirmation_code', { email })
|
||||
|
||||
const Button = isCiam ? DSButton : OLButton
|
||||
const buttonSize = isCiam ? 'lg' : undefined
|
||||
|
||||
const SixDigits = isCiam ? CIAMSixDigitsInput : OLSixDigitsInput
|
||||
const FormText = isCiam ? DSFormText : OLFormText
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={submitHandler}
|
||||
@@ -191,53 +222,54 @@ export function ConfirmEmailForm({
|
||||
outerErrorDisplay={outerErrorDisplay}
|
||||
/>
|
||||
|
||||
<OLFormLabel htmlFor="one-time-code">
|
||||
{isModal
|
||||
? t('enter_the_code', { email })
|
||||
: t('enter_the_confirmation_code', { email })}
|
||||
</OLFormLabel>
|
||||
<input
|
||||
{isCiam && <p>{longLabel}</p>}
|
||||
|
||||
{isCiam ? (
|
||||
<DSFormLabel htmlFor="one-time-code">
|
||||
{t('verification_code')}
|
||||
</DSFormLabel>
|
||||
) : (
|
||||
<OLFormLabel htmlFor="one-time-code">{longLabel}</OLFormLabel>
|
||||
)}
|
||||
|
||||
<SixDigits
|
||||
id="one-time-code"
|
||||
className="form-control"
|
||||
inputMode="numeric"
|
||||
required
|
||||
value={confirmationCode}
|
||||
onChange={changeHandler}
|
||||
data-ol-dirty={feedback ? 'true' : undefined}
|
||||
maxLength={6}
|
||||
autoComplete="one-time-code"
|
||||
autoFocus // eslint-disable-line jsx-a11y/no-autofocus
|
||||
disabled={!!outerErrorDisplay}
|
||||
/>
|
||||
<div aria-live="polite">
|
||||
{feedback?.type === 'input' && (
|
||||
<div className="small text-danger">
|
||||
<MaterialIcon className="icon" type="error" />
|
||||
<div>
|
||||
<ErrorMessage error={feedback.message} />
|
||||
</div>
|
||||
</div>
|
||||
<FormText type="error" marginless>
|
||||
<ErrorMessage error={feedback.message} />
|
||||
</FormText>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="form-actions">
|
||||
<OLButton
|
||||
<Button
|
||||
size={buttonSize}
|
||||
disabled={isResending || !!outerErrorDisplay}
|
||||
type="submit"
|
||||
isLoading={isConfirming}
|
||||
loadingLabel={t('confirming')}
|
||||
>
|
||||
{t('confirm')}
|
||||
</OLButton>
|
||||
<OLButton
|
||||
</Button>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size={buttonSize}
|
||||
disabled={isConfirming}
|
||||
onClick={resendHandler}
|
||||
isLoading={isResending}
|
||||
loadingLabel={t('resending_confirmation_code')}
|
||||
>
|
||||
{t('resend_confirmation_code')}
|
||||
</OLButton>
|
||||
</Button>
|
||||
{onCancel && (
|
||||
<OLButton
|
||||
variant="danger-ghost"
|
||||
@@ -248,6 +280,25 @@ export function ConfirmEmailForm({
|
||||
</OLButton>
|
||||
)}
|
||||
</div>
|
||||
{isCiam && flow === 'registration' && (
|
||||
<div className="mt-4 mb-2 text-center ">
|
||||
<Trans
|
||||
i18nKey="use_a_different_email"
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
|
||||
<a
|
||||
href="/register"
|
||||
onClick={() =>
|
||||
sendMB('email-verification-click', {
|
||||
button: 'change-email',
|
||||
flow,
|
||||
})
|
||||
}
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
@@ -281,10 +332,12 @@ function ConfirmEmailSuccessfullForm({
|
||||
successMessage,
|
||||
successButtonText,
|
||||
redirectTo,
|
||||
autoRedirect = false,
|
||||
}: {
|
||||
successMessage: React.ReactNode
|
||||
successButtonText: string
|
||||
redirectTo: string
|
||||
autoRedirect?: number | false
|
||||
}) {
|
||||
const location = useLocation()
|
||||
const submitHandler = (e: FormEvent<HTMLFormElement>) => {
|
||||
@@ -292,15 +345,24 @@ function ConfirmEmailSuccessfullForm({
|
||||
location.assign(redirectTo)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (autoRedirect) {
|
||||
const timer = setTimeout(() => location.assign(redirectTo), autoRedirect)
|
||||
return () => clearTimeout(timer)
|
||||
}
|
||||
}, [autoRedirect, location, redirectTo])
|
||||
|
||||
return (
|
||||
<form onSubmit={submitHandler}>
|
||||
<form onSubmit={submitHandler} className="confirm-email-success-form">
|
||||
<div aria-live="polite">{successMessage}</div>
|
||||
|
||||
<div className="form-actions">
|
||||
<OLButton type="submit" variant="primary">
|
||||
{successButtonText}
|
||||
</OLButton>
|
||||
</div>
|
||||
{!autoRedirect && (
|
||||
<div className="form-actions">
|
||||
<OLButton type="submit" variant="primary">
|
||||
{successButtonText}
|
||||
</OLButton>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { forwardRef, ReactNode } from 'react'
|
||||
import { Button, ButtonProps } from 'react-bootstrap'
|
||||
import { Button, ButtonProps, Spinner } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import classNames from 'classnames'
|
||||
|
||||
type DSButtonProps = Pick<
|
||||
ButtonProps,
|
||||
@@ -22,6 +24,8 @@ type DSButtonProps = Pick<
|
||||
leadingIcon?: ReactNode
|
||||
trailingIcon?: ReactNode
|
||||
variant?: 'primary' | 'secondary' | 'tertiary' | 'danger'
|
||||
isLoading?: boolean
|
||||
loadingLabel?: string
|
||||
}
|
||||
|
||||
const DSButton = forwardRef<HTMLButtonElement, DSButtonProps>(
|
||||
@@ -29,6 +33,8 @@ const DSButton = forwardRef<HTMLButtonElement, DSButtonProps>(
|
||||
{
|
||||
children,
|
||||
leadingIcon,
|
||||
isLoading = false,
|
||||
loadingLabel,
|
||||
trailingIcon,
|
||||
variant = 'primary',
|
||||
size,
|
||||
@@ -36,16 +42,40 @@ const DSButton = forwardRef<HTMLButtonElement, DSButtonProps>(
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const buttonClassName = classNames('d-inline-grid btn-ds', {
|
||||
'button-loading': isLoading,
|
||||
})
|
||||
|
||||
const loadingSpinnerClassName =
|
||||
size === 'lg' ? 'loading-spinner-large' : 'loading-spinner-small'
|
||||
|
||||
return (
|
||||
<Button
|
||||
className="d-inline-grid btn-ds"
|
||||
className={buttonClassName}
|
||||
variant={variant}
|
||||
size={size}
|
||||
{...props}
|
||||
ref={ref}
|
||||
disabled={isLoading || props.disabled}
|
||||
data-ol-loading={isLoading}
|
||||
role={undefined}
|
||||
>
|
||||
<span className="button-content">
|
||||
{isLoading && (
|
||||
<span className="spinner-container">
|
||||
<Spinner
|
||||
size="sm"
|
||||
animation="border"
|
||||
aria-hidden="true"
|
||||
className={loadingSpinnerClassName}
|
||||
/>
|
||||
<span className="visually-hidden">
|
||||
{loadingLabel ?? t('loading')}
|
||||
</span>
|
||||
</span>
|
||||
)}
|
||||
<span className="button-content" aria-hidden={isLoading}>
|
||||
{leadingIcon}
|
||||
{children}
|
||||
{trailingIcon}
|
||||
|
||||
@@ -7,7 +7,10 @@ type TextType = 'success' | 'error'
|
||||
|
||||
export type FormTextProps = MergeAndOverride<
|
||||
BS5FormTextProps,
|
||||
{ type?: TextType }
|
||||
{
|
||||
type?: TextType
|
||||
marginless?: boolean
|
||||
}
|
||||
>
|
||||
|
||||
const typeClasses = {
|
||||
@@ -31,10 +34,18 @@ function FormTextIcon({ type }: { type?: TextType }) {
|
||||
}
|
||||
}
|
||||
|
||||
function DSFormText({ type, children, className, ...rest }: FormTextProps) {
|
||||
function DSFormText({
|
||||
type,
|
||||
marginless,
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: FormTextProps) {
|
||||
return (
|
||||
<Form.Text
|
||||
className={classnames('form-text-ds', className, getFormTextClass(type))}
|
||||
className={classnames('form-text-ds', className, getFormTextClass(type), {
|
||||
marginless,
|
||||
})}
|
||||
{...rest}
|
||||
>
|
||||
<span className="form-text-inner-ds">
|
||||
|
||||
@@ -9,6 +9,7 @@ export type FormTextProps = MergeAndOverride<
|
||||
BS5FormTextProps,
|
||||
{
|
||||
type?: TextType
|
||||
marginless?: boolean
|
||||
}
|
||||
>
|
||||
|
||||
@@ -38,13 +39,14 @@ function FormTextIcon({ type }: { type?: TextType }) {
|
||||
|
||||
function FormText({
|
||||
type = 'default',
|
||||
marginless,
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: FormTextProps) {
|
||||
return (
|
||||
<Form.Text
|
||||
className={classnames(className, getFormTextClass(type))}
|
||||
className={classnames(className, getFormTextClass(type), { marginless })}
|
||||
{...rest}
|
||||
>
|
||||
<span className="form-text-inner">
|
||||
|
||||
@@ -26,7 +26,7 @@ const meta: Meta<typeof DSButton> = {
|
||||
},
|
||||
parameters: {
|
||||
controls: {
|
||||
include: ['children', 'disabled', 'size', 'variant'],
|
||||
include: ['children', 'disabled', 'isLoading', 'size', 'variant'],
|
||||
},
|
||||
...figmaDesignUrl(
|
||||
'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=4565-2932&m=dev'
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { ComponentProps, useEffect, useState } from 'react'
|
||||
import { Meta } from '@storybook/react'
|
||||
import { figmaDesignUrl } from '../../../.storybook/utils/figma-design-url'
|
||||
import DSFormGroup from '@/shared/components/ds/ds-form-group'
|
||||
import DSFormLabel from '@/shared/components/ds/ds-form-label'
|
||||
import CIAMSixDigitsInput from '@/features/settings/components/emails/ciam-six-digits-input'
|
||||
|
||||
type Args = ComponentProps<typeof CIAMSixDigitsInput>
|
||||
|
||||
export const SixDigitsInput = ({ value, ...args }: Args) => {
|
||||
const [state, setState] = useState(value)
|
||||
useEffect(() => setState(value), [value])
|
||||
return (
|
||||
<div className="ciam-enabled">
|
||||
<DSFormGroup controlId="form-control-id">
|
||||
<DSFormLabel>Form input label</DSFormLabel>
|
||||
<CIAMSixDigitsInput
|
||||
id="form-control-id"
|
||||
{...args}
|
||||
value={state}
|
||||
onChange={e => setState(e.target.value)}
|
||||
/>
|
||||
</DSFormGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const meta: Meta<typeof SixDigitsInput> = {
|
||||
title: 'Shared / DS Components',
|
||||
component: SixDigitsInput,
|
||||
argTypes: {
|
||||
value: { control: 'text' },
|
||||
},
|
||||
parameters: {
|
||||
controls: {
|
||||
include: ['value'],
|
||||
},
|
||||
...figmaDesignUrl(
|
||||
'https://www.figma.com/design/aJQlecvqCS9Ry8b6JA1lQN/DS---Components?node-id=6318-428&t=pcx9KKzhlzpRmA4S-0'
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
export default meta
|
||||
@@ -2,3 +2,4 @@
|
||||
@import 'ciam-layout';
|
||||
@import 'ciam-variables';
|
||||
@import 'ciam-register';
|
||||
@import 'ciam-six-digits';
|
||||
|
||||
@@ -76,6 +76,7 @@
|
||||
border-radius: var(--ds-border-radius-400);
|
||||
max-width: 464px;
|
||||
margin: 0 auto;
|
||||
min-height: 500px;
|
||||
|
||||
@include media-breakpoint-up(sm) {
|
||||
padding: var(--ds-spacing-1300);
|
||||
|
||||
26
services/web/frontend/stylesheets/ciam/ciam-six-digits.scss
Normal file
26
services/web/frontend/stylesheets/ciam/ciam-six-digits.scss
Normal file
@@ -0,0 +1,26 @@
|
||||
@import '../ds/mixins';
|
||||
|
||||
.ciam-six-digits-container {
|
||||
position: relative;
|
||||
min-width: 180px;
|
||||
|
||||
.ciam-six-digits-input {
|
||||
@include ds-heading-md-regular;
|
||||
|
||||
letter-spacing: 0.3em;
|
||||
padding-left: calc(50% - 3.3em) !important;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.ciam-six-digits-dash {
|
||||
@include ds-heading-md-regular;
|
||||
|
||||
pointer-events: none;
|
||||
color: var(--ds-color-neutral-400);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,10 @@
|
||||
|
||||
.ciam-enabled,
|
||||
.website-redesign:not(.application-page) .ciam-enabled .notification {
|
||||
// Links
|
||||
// used in services/web/frontend/stylesheets/base/links.scss
|
||||
// Links, used in services/web/frontend/stylesheets/base/links.scss
|
||||
--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-color-dark: fuchsia;
|
||||
--link-hover-color-dark: fuchsia;
|
||||
--link-visited-color-dark: fuchsia;
|
||||
}
|
||||
|
||||
@@ -120,6 +120,13 @@
|
||||
line-height: var(--line-height-02);
|
||||
margin-top: var(--spacing-04);
|
||||
}
|
||||
|
||||
&.marginless {
|
||||
&,
|
||||
.form-text-inner {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-label {
|
||||
|
||||
@@ -78,4 +78,37 @@
|
||||
$disabled-background: var(--ds-color-neutral-100)
|
||||
);
|
||||
}
|
||||
|
||||
&.button-loading {
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
.loading-spinner-small {
|
||||
border-width: 0.25em;
|
||||
height: 1.25rem;
|
||||
width: 1.25rem;
|
||||
}
|
||||
|
||||
.loading-spinner-large {
|
||||
border-width: 0.25em;
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
border-right-color: color-mix(
|
||||
in srgb,
|
||||
var(--bs-btn-color) 32%,
|
||||
var(--bs-btn-bg)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the text when the spinner is visible
|
||||
& > [aria-hidden='true'] {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,13 @@ input.form-control.form-control-ds {
|
||||
@include ds-body-sm-regular;
|
||||
}
|
||||
|
||||
&.marginless {
|
||||
&,
|
||||
.form-text-inner-ds {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.ciam-form-text-icon {
|
||||
font-size: math.div(20em, 14);
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
@import 'register';
|
||||
@import 'plans';
|
||||
@import 'onboarding-confirm-email';
|
||||
@import 'onboarding-confirm-email-ciam';
|
||||
@import 'secondary-confirm-email';
|
||||
@import 'onboarding';
|
||||
@import 'admin/admin';
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
#onboarding-confirm-email .ciam-enabled {
|
||||
.animated-tick {
|
||||
height: var(--ds-spacing-1200);
|
||||
width: var(--ds-spacing-1200);
|
||||
}
|
||||
|
||||
.ciam-email-confirmed-title {
|
||||
margin-top: var(--ds-spacing-800);
|
||||
margin-bottom: var(--ds-spacing-600);
|
||||
}
|
||||
|
||||
.confirm-email-success-form {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.confirm-email-form .confirm-email-form-inner {
|
||||
margin: auto;
|
||||
max-width: 480px;
|
||||
|
||||
.notification {
|
||||
margin-bottom: var(--spacing-05);
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
display: flex;
|
||||
gap: var(--spacing-03);
|
||||
padding: var(--spacing-02);
|
||||
}
|
||||
|
||||
.form-label {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
margin-top: var(--ds-spacing-800);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--ds-spacing-400);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1253,6 +1253,7 @@
|
||||
"let_us_know_how_we_can_help": "Let us know how we can help",
|
||||
"let_us_know_what_you_think": "Let us know what you think",
|
||||
"lets_get_those_premium_features": "Let’s get those premium features up and running for you straightaway. You’ll be billed <0>__paymentAmount__</0> using the payment details we have for you.",
|
||||
"lets_get_you_set_up": "Let’s get you set up.",
|
||||
"libraries": "Libraries",
|
||||
"library": "Library",
|
||||
"license": "License",
|
||||
@@ -2651,6 +2652,7 @@
|
||||
"value_must_be_at_least_x": "Value must be at least __value__",
|
||||
"vat": "VAT",
|
||||
"vat_number": "VAT Number",
|
||||
"verification_code": "Verification code",
|
||||
"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",
|
||||
@@ -2808,6 +2810,7 @@
|
||||
"your_current_plan_gives_you": "By pausing your subscription, you’ll be able to access your premium features faster when you need them again.",
|
||||
"your_current_plan_supports_up_to_x_licenses": "Your current plan supports up to __users__ licenses.",
|
||||
"your_current_project_will_revert_to_the_version_from_time": "Your current project will revert to the version from __timestamp__",
|
||||
"your_email_is_confirmed": "Your email is confirmed.",
|
||||
"your_feedback_matters_answer_two_quick_questions": "Your feedback matters! Answer two quick questions.",
|
||||
"your_git_access_info": "Your Git authentication tokens should be entered whenever you’re prompted for a password.",
|
||||
"your_git_access_info_bullet_1": "You can have up to 10 tokens.",
|
||||
|
||||
Reference in New Issue
Block a user