Login: Added custom validation for missing password and user/email on the login form (#20233)
* Added custom validation for missing password and user/email * Changed some of the logic behind custom validation, so it now uses aria-errormessage * fix: imports from src folder instead * build(deps-dev): bump vite to 7.2.0 * formatting * fix: moves the form into the login.page.element.ts component to better control submission * fix: creates elements globally * fix: adds id back to form * fix: no need to store references to all form elements * fix: errormessage should show with password field in a span as well * fix: checks validity of form * fix: constructs form in auth.element.ts anyway and append localization to validation and add oninput and onblur * chore: fixes import paths * fix: fixes special case where ?status was not reset * fix: changes wording in english * fix: removes duplicate en-us keys * feat: adds ariaLive and role attributes * fix: always clears the text * fix: username required validation should switch between username and email * package-lock.json updated on (re)install * Renamed SVG eye icon filenames to be conventional and kebab-cased. --------- Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Co-authored-by: leekelleher <leekelleher@gmail.com>
This commit is contained in:
committed by
GitHub
parent
bce85e1e88
commit
73fd52aeea
178
src/Umbraco.Web.UI.Login/package-lock.json
generated
178
src/Umbraco.Web.UI.Login/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,7 @@
|
||||
"@umbraco-cms/backoffice": "^16.2.0",
|
||||
"msw": "^2.11.3",
|
||||
"typescript": "^5.9.3",
|
||||
"vite": "^7.1.11"
|
||||
"vite": "^7.2.0"
|
||||
},
|
||||
"msw": {
|
||||
"workerDirectory": [
|
||||
|
||||
|
Before Width: | Height: | Size: 452 B After Width: | Height: | Size: 452 B |
|
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 273 B |
@@ -1,4 +1,19 @@
|
||||
#umb-login-form #username-input {
|
||||
.errormessage {
|
||||
color: var(--uui-color-invalid-standalone);
|
||||
display: none;
|
||||
margin-top: var(--uui-size-1);
|
||||
}
|
||||
|
||||
.errormessage.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
uui-form-layout-item {
|
||||
margin-top: var(--uui-size-space-4);
|
||||
margin-bottom: var(--uui-size-space-4);
|
||||
}
|
||||
|
||||
#username-input {
|
||||
width: 100%;
|
||||
height: var(--input-height);
|
||||
box-sizing: border-box;
|
||||
@@ -9,21 +24,16 @@
|
||||
padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px);
|
||||
}
|
||||
|
||||
#umb-login-form uui-form-layout-item {
|
||||
margin-top: var(--uui-size-space-4);
|
||||
margin-bottom: var(--uui-size-space-4);
|
||||
}
|
||||
|
||||
#umb-login-form #username-input:focus-within {
|
||||
#username-input:focus-within {
|
||||
border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1));
|
||||
outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus);
|
||||
}
|
||||
|
||||
#umb-login-form #username-input:hover:not(:focus-within) {
|
||||
#username-input:hover:not(:focus-within) {
|
||||
border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2));
|
||||
}
|
||||
|
||||
#umb-login-form #password-input-span button {
|
||||
#password-show-toggle {
|
||||
color: var(--uui-color-default-standalone);
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
@@ -39,12 +49,12 @@
|
||||
transition-timing-function: linear;
|
||||
}
|
||||
|
||||
#umb-login-form #password-input-span button:hover {
|
||||
#password-show-toggle:hover {
|
||||
color: var(--uui-color-default-emphasis);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#umb-login-form #password-input-span {
|
||||
#password-input-span {
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
@@ -60,7 +70,7 @@
|
||||
padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px);
|
||||
}
|
||||
|
||||
#umb-login-form #password-input-span input {
|
||||
#password-input {
|
||||
flex-grow: 1;
|
||||
align-self: stretch;
|
||||
min-width: 0;
|
||||
@@ -70,15 +80,15 @@
|
||||
outline-style: none;
|
||||
}
|
||||
|
||||
#umb-login-form #password-input-span:focus-within {
|
||||
#password-input-span:focus-within {
|
||||
border-color: var(--uui-input-border-color-focus, var(--uui-color-border-emphasis, #a1a1a1));
|
||||
outline: calc(2px * var(--uui-show-focus-outline, 1)) solid var(--uui-color-focus);
|
||||
}
|
||||
|
||||
#umb-login-form #password-input-span:hover:not(:focus-within) {
|
||||
#password-input-span:hover:not(:focus-within) {
|
||||
border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2));
|
||||
}
|
||||
|
||||
#umb-login-form input::-ms-reveal {
|
||||
#password-input::-ms-reveal {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -3,15 +3,15 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import type { InputType, UUIFormLayoutItemElement } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
|
||||
|
||||
import { UMB_AUTH_CONTEXT, UmbAuthContext } from './contexts';
|
||||
import { UmbSlimBackofficeController } from './controllers';
|
||||
import { UMB_AUTH_CONTEXT, UmbAuthContext } from './contexts/index.js';
|
||||
import { UmbSlimBackofficeController } from './controllers/index.js';
|
||||
|
||||
// We import the authStyles here so that we can inline it in the shadow DOM that is created outside of the UmbAuthElement.
|
||||
import authStyles from './auth-styles.css?inline';
|
||||
|
||||
// Import the SVG files
|
||||
import openEyeSVG from '../public/openEye.svg?raw';
|
||||
import closedEyeSVG from '../public/closedEye.svg?raw';
|
||||
import svgEyeOpen from './assets/eye-open.svg?raw';
|
||||
import svgEyeClosed from './assets/eye-closed.svg?raw';
|
||||
|
||||
// Import the main bundle
|
||||
import { extensions } from './umbraco-package.js';
|
||||
@@ -21,6 +21,7 @@ const createInput = (opts: {
|
||||
type: InputType;
|
||||
name: string;
|
||||
autocomplete: AutoFill;
|
||||
errorId: string;
|
||||
inputmode: string;
|
||||
autofocus?: boolean;
|
||||
}) => {
|
||||
@@ -31,7 +32,10 @@ const createInput = (opts: {
|
||||
input.id = opts.id;
|
||||
input.required = true;
|
||||
input.inputMode = opts.inputmode;
|
||||
input.setAttribute('aria-errormessage', opts.errorId);
|
||||
input.autofocus = opts.autofocus || false;
|
||||
input.className = 'input';
|
||||
|
||||
return input;
|
||||
};
|
||||
|
||||
@@ -46,6 +50,14 @@ const createLabel = (opts: { forId: string; localizeAlias: string; localizeFallb
|
||||
return label;
|
||||
};
|
||||
|
||||
const createValidationMessage = (errorId: string) => {
|
||||
const validationElement = document.createElement('div');
|
||||
validationElement.className = 'errormessage';
|
||||
validationElement.id = errorId;
|
||||
validationElement.role = 'alert';
|
||||
return validationElement;
|
||||
};
|
||||
|
||||
const createShowPasswordToggleButton = (opts: {
|
||||
id: string;
|
||||
name: string;
|
||||
@@ -58,7 +70,7 @@ const createShowPasswordToggleButton = (opts: {
|
||||
button.name = opts.name;
|
||||
button.type = 'button';
|
||||
|
||||
button.innerHTML = openEyeSVG;
|
||||
button.innerHTML = svgEyeOpen;
|
||||
|
||||
button.onclick = () => {
|
||||
const passwordInput = document.getElementById('password-input') as HTMLInputElement;
|
||||
@@ -66,11 +78,11 @@ const createShowPasswordToggleButton = (opts: {
|
||||
if (passwordInput.type === 'password') {
|
||||
passwordInput.type = 'text';
|
||||
button.ariaLabel = opts.ariaLabelHidePassword;
|
||||
button.innerHTML = closedEyeSVG;
|
||||
button.innerHTML = svgEyeClosed;
|
||||
} else {
|
||||
passwordInput.type = 'password';
|
||||
button.ariaLabel = opts.ariaLabelShowPassword;
|
||||
button.innerHTML = openEyeSVG;
|
||||
button.innerHTML = svgEyeOpen;
|
||||
}
|
||||
|
||||
passwordInput.focus();
|
||||
@@ -87,44 +99,67 @@ const createShowPasswordToggleItem = (button: HTMLButtonElement) => {
|
||||
return span;
|
||||
};
|
||||
|
||||
const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement) => {
|
||||
const createFormLayoutItem = (label: HTMLLabelElement, input: HTMLInputElement, localizationKey: string) => {
|
||||
const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement;
|
||||
const errorId = input.getAttribute('aria-errormessage') || input.id + '-error';
|
||||
|
||||
formLayoutItem.appendChild(label);
|
||||
formLayoutItem.appendChild(input);
|
||||
|
||||
const validationMessage = createValidationMessage(errorId);
|
||||
formLayoutItem.appendChild(validationMessage);
|
||||
|
||||
// Bind validation
|
||||
input.oninput = () => validateInput(input, validationMessage, localizationKey);
|
||||
input.onblur = () => validateInput(input, validationMessage, localizationKey);
|
||||
|
||||
return formLayoutItem;
|
||||
};
|
||||
|
||||
const createFormLayoutPasswordItem = (
|
||||
label: HTMLLabelElement,
|
||||
input: HTMLInputElement,
|
||||
showPasswordToggle: HTMLSpanElement
|
||||
showPasswordToggle: HTMLSpanElement,
|
||||
requiredMessageKey: string
|
||||
) => {
|
||||
const formLayoutItem = document.createElement('uui-form-layout-item') as UUIFormLayoutItemElement;
|
||||
const errorId = input.getAttribute('aria-errormessage') || input.id + '-error';
|
||||
|
||||
formLayoutItem.appendChild(label);
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.id = 'password-input-span';
|
||||
span.appendChild(input);
|
||||
span.appendChild(showPasswordToggle);
|
||||
formLayoutItem.appendChild(span);
|
||||
|
||||
const validationMessage = createValidationMessage(errorId);
|
||||
formLayoutItem.appendChild(validationMessage);
|
||||
|
||||
// Bind validation
|
||||
input.oninput = () => validateInput(input, validationMessage, requiredMessageKey);
|
||||
input.onblur = () => validateInput(input, validationMessage, requiredMessageKey);
|
||||
|
||||
return formLayoutItem;
|
||||
};
|
||||
|
||||
const createForm = (elements: HTMLElement[]) => {
|
||||
const styles = document.createElement('style');
|
||||
styles.innerHTML = authStyles;
|
||||
const form = document.createElement('form');
|
||||
form.id = 'umb-login-form';
|
||||
form.name = 'login-form';
|
||||
form.spellcheck = false;
|
||||
const validateInput = (input: HTMLInputElement, validationElement: HTMLElement, requiredMessage = '') => {
|
||||
validationElement.innerHTML = '';
|
||||
if (input.validity.valid) {
|
||||
input.removeAttribute('aria-invalid');
|
||||
validationElement.classList.remove('active');
|
||||
validationElement.ariaLive = 'off';
|
||||
} else {
|
||||
input.setAttribute('aria-invalid', 'true');
|
||||
|
||||
elements.push(styles);
|
||||
elements.forEach((element) => form.appendChild(element));
|
||||
const localizeElement = document.createElement('umb-localize');
|
||||
localizeElement.innerHTML = input.validationMessage;
|
||||
localizeElement.key = requiredMessage;
|
||||
validationElement.appendChild(localizeElement);
|
||||
|
||||
return form;
|
||||
validationElement.classList.add('active');
|
||||
validationElement.ariaLive = 'assertive';
|
||||
}
|
||||
};
|
||||
|
||||
@customElement('umb-auth')
|
||||
@@ -168,16 +203,6 @@ export default class UmbAuthElement extends UmbLitElement {
|
||||
*/
|
||||
protected flow?: 'mfa' | 'reset-password' | 'invite-user';
|
||||
|
||||
_form?: HTMLFormElement;
|
||||
_usernameLayoutItem?: UUIFormLayoutItemElement;
|
||||
_passwordLayoutItem?: UUIFormLayoutItemElement;
|
||||
_usernameInput?: HTMLInputElement;
|
||||
_passwordInput?: HTMLInputElement;
|
||||
_usernameLabel?: HTMLLabelElement;
|
||||
_passwordLabel?: HTMLLabelElement;
|
||||
_passwordShowPasswordToggleItem?: HTMLSpanElement;
|
||||
_passwordShowPasswordToggleButton?: HTMLButtonElement;
|
||||
|
||||
#authContext = new UmbAuthContext(this, UMB_AUTH_CONTEXT);
|
||||
|
||||
constructor() {
|
||||
@@ -186,6 +211,16 @@ export default class UmbAuthElement extends UmbLitElement {
|
||||
(this as unknown as EventTarget).addEventListener('umb-login-flow', (e) => {
|
||||
if (e instanceof CustomEvent) {
|
||||
this.flow = e.detail.flow || undefined;
|
||||
if (typeof e.detail.status !== 'undefined') {
|
||||
const searchParams = new URLSearchParams(window.location.search);
|
||||
if (e.detail.status === null) {
|
||||
searchParams.delete('status');
|
||||
} else {
|
||||
searchParams.set('status', e.detail.status);
|
||||
}
|
||||
const newRelativePathQuery = window.location.pathname + '?' + searchParams.toString();
|
||||
window.history.pushState(null, '', newRelativePathQuery);
|
||||
}
|
||||
}
|
||||
this.requestUpdate();
|
||||
});
|
||||
@@ -229,18 +264,6 @@ export default class UmbAuthElement extends UmbLitElement {
|
||||
});
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
this._usernameLayoutItem?.remove();
|
||||
this._passwordLayoutItem?.remove();
|
||||
this._usernameLabel?.remove();
|
||||
this._usernameInput?.remove();
|
||||
this._passwordLabel?.remove();
|
||||
this._passwordInput?.remove();
|
||||
this._passwordShowPasswordToggleItem?.remove();
|
||||
this._passwordShowPasswordToggleButton?.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the login form and adds it to the DOM in the default slot.
|
||||
* This is done to avoid having to deal with the shadow DOM, which is not supported in Google Chrome for autocomplete/autofill.
|
||||
@@ -249,48 +272,65 @@ export default class UmbAuthElement extends UmbLitElement {
|
||||
* @private
|
||||
*/
|
||||
#initializeForm() {
|
||||
this._usernameInput = createInput({
|
||||
const usernameInput = createInput({
|
||||
id: 'username-input',
|
||||
type: 'text',
|
||||
name: 'username',
|
||||
autocomplete: 'username',
|
||||
errorId: 'username-input-error',
|
||||
inputmode: this.usernameIsEmail ? 'email' : '',
|
||||
autofocus: true,
|
||||
});
|
||||
this._passwordInput = createInput({
|
||||
const passwordInput = createInput({
|
||||
id: 'password-input',
|
||||
type: 'password',
|
||||
name: 'password',
|
||||
autocomplete: 'current-password',
|
||||
errorId: 'password-input-error',
|
||||
inputmode: '',
|
||||
});
|
||||
this._passwordShowPasswordToggleButton = createShowPasswordToggleButton({
|
||||
const passwordShowPasswordToggleButton = createShowPasswordToggleButton({
|
||||
id: 'password-show-toggle',
|
||||
name: 'password-show-toggle',
|
||||
ariaLabelShowPassword: this.localize.term('auth_showPassword'),
|
||||
ariaLabelHidePassword: this.localize.term('auth_hidePassword'),
|
||||
});
|
||||
this._passwordShowPasswordToggleItem = createShowPasswordToggleItem(this._passwordShowPasswordToggleButton);
|
||||
this._usernameLabel = createLabel({
|
||||
const passwordShowPasswordToggleItem = createShowPasswordToggleItem(passwordShowPasswordToggleButton);
|
||||
const usernameLabel = createLabel({
|
||||
forId: 'username-input',
|
||||
localizeAlias: this.usernameIsEmail ? 'auth_email' : 'auth_username',
|
||||
localizeFallback: this.usernameIsEmail ? 'Email' : 'Username',
|
||||
});
|
||||
this._passwordLabel = createLabel({
|
||||
const passwordLabel = createLabel({
|
||||
forId: 'password-input',
|
||||
localizeAlias: 'auth_password',
|
||||
localizeFallback: 'Password',
|
||||
});
|
||||
this._usernameLayoutItem = createFormLayoutItem(this._usernameLabel, this._usernameInput);
|
||||
this._passwordLayoutItem = createFormLayoutPasswordItem(
|
||||
this._passwordLabel,
|
||||
this._passwordInput,
|
||||
this._passwordShowPasswordToggleItem
|
||||
const usernameLayoutItem = createFormLayoutItem(
|
||||
usernameLabel,
|
||||
usernameInput,
|
||||
this.usernameIsEmail ? 'auth_requiredEmailValidationMessage' : 'auth_requiredUsernameValidationMessage'
|
||||
);
|
||||
const passwordLayoutItem = createFormLayoutPasswordItem(
|
||||
passwordLabel,
|
||||
passwordInput,
|
||||
passwordShowPasswordToggleItem,
|
||||
'auth_requiredPasswordValidationMessage'
|
||||
);
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = authStyles;
|
||||
document.head.appendChild(style);
|
||||
|
||||
this._form = createForm([this._usernameLayoutItem, this._passwordLayoutItem]);
|
||||
const form = document.createElement('form');
|
||||
form.id = 'umb-login-form';
|
||||
form.name = 'login-form';
|
||||
form.spellcheck = false;
|
||||
form.setAttribute('novalidate', '');
|
||||
|
||||
this.insertAdjacentElement('beforeend', this._form);
|
||||
form.appendChild(usernameLayoutItem);
|
||||
form.appendChild(passwordLayoutItem);
|
||||
|
||||
this.insertAdjacentElement('beforeend', form);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -347,12 +387,11 @@ export default class UmbAuthElement extends UmbLitElement {
|
||||
return html` <umb-invite-page></umb-invite-page>`;
|
||||
|
||||
default:
|
||||
return html` <umb-login-page
|
||||
?allow-password-reset=${this.allowPasswordReset}
|
||||
?username-is-email=${this.usernameIsEmail}>
|
||||
return html`
|
||||
<umb-login-page ?allow-password-reset=${this.allowPasswordReset} ?username-is-email=${this.usernameIsEmail}>
|
||||
<slot></slot>
|
||||
<slot name="subheadline" slot="subheadline"></slot>
|
||||
</umb-login-page>`;
|
||||
</umb-login-page>
|
||||
`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export default class UmbBackToLoginButtonElement extends UmbLitElement {
|
||||
}
|
||||
|
||||
#handleClick() {
|
||||
this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: 'login' } }));
|
||||
this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: 'login', status: null } }));
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
|
||||
@@ -1,8 +1,17 @@
|
||||
import type { UUIButtonState } from '@umbraco-cms/backoffice/external/uui';
|
||||
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
|
||||
import { css, type CSSResultGroup, html, nothing, when, customElement, property, queryAssignedElements, state } from '@umbraco-cms/backoffice/external/lit';
|
||||
import {
|
||||
css,
|
||||
html,
|
||||
nothing,
|
||||
when,
|
||||
customElement,
|
||||
property,
|
||||
queryAssignedElements,
|
||||
state,
|
||||
} from '@umbraco-cms/backoffice/external/lit';
|
||||
|
||||
import { UMB_AUTH_CONTEXT } from '../../contexts';
|
||||
import { UMB_AUTH_CONTEXT } from '../../contexts/index.js';
|
||||
|
||||
@customElement('umb-login-page')
|
||||
export default class UmbLoginPageElement extends UmbLitElement {
|
||||
@@ -42,23 +51,29 @@ export default class UmbLoginPageElement extends UmbLitElement {
|
||||
|
||||
if (!this.#formElement) return;
|
||||
|
||||
// We need to listen for the enter key to submit the form, because the uui-button does not support the native input fields submit event
|
||||
this.#formElement.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
this.#onSubmitClick();
|
||||
}
|
||||
});
|
||||
|
||||
this.#formElement.onsubmit = this.#handleSubmit;
|
||||
}
|
||||
|
||||
#handleSubmit = async (e: SubmitEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
this._loginError = '';
|
||||
this._loginState = undefined;
|
||||
if (!this.#authContext) return;
|
||||
|
||||
const form = e.target as HTMLFormElement;
|
||||
if (!form) return;
|
||||
|
||||
if (!form?.checkValidity()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(form);
|
||||
|
||||
const username = formData.get('username') as string;
|
||||
@@ -66,8 +81,6 @@ export default class UmbLoginPageElement extends UmbLitElement {
|
||||
const persist = formData.has('persist');
|
||||
|
||||
if (!username || !password) {
|
||||
this._loginError = this.localize.term('auth_userFailedLogin');
|
||||
this._loginState = 'failed';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -135,11 +148,8 @@ export default class UmbLoginPageElement extends UmbLitElement {
|
||||
<div id="secondary-actions">
|
||||
${when(
|
||||
this.supportPersistLogin,
|
||||
() => html`
|
||||
<uui-form-layout-item>
|
||||
<uui-checkbox
|
||||
name="persist"
|
||||
.label=${this.localize.term('auth_rememberMe')}>
|
||||
() => html` <uui-form-layout-item>
|
||||
<uui-checkbox name="persist" .label=${this.localize.term('auth_rememberMe')}>
|
||||
<umb-localize key="auth_rememberMe">Remember me</umb-localize>
|
||||
</uui-checkbox>
|
||||
</uui-form-layout-item>`
|
||||
@@ -147,17 +157,16 @@ export default class UmbLoginPageElement extends UmbLitElement {
|
||||
${when(
|
||||
this.allowPasswordReset,
|
||||
() =>
|
||||
html`
|
||||
<button type="button" id="forgot-password" @click=${this.#handleForgottenPassword}>
|
||||
html` <button type="button" id="forgot-password" @click=${this.#handleForgottenPassword}>
|
||||
<umb-localize key="auth_forgottenPassword">Forgotten password?</umb-localize>
|
||||
</button>`
|
||||
)}
|
||||
</div>
|
||||
<uui-button
|
||||
@click=${this.#onSubmitClick}
|
||||
type="submit"
|
||||
id="umb-login-button"
|
||||
look="primary"
|
||||
@click=${this.#onSubmitClick}
|
||||
.label=${this.localize.term('auth_login')}
|
||||
color="default"
|
||||
.state=${this._loginState}></uui-button>
|
||||
@@ -176,7 +185,7 @@ export default class UmbLoginPageElement extends UmbLitElement {
|
||||
this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: 'reset' } }));
|
||||
}
|
||||
|
||||
static styles: CSSResultGroup = [
|
||||
static readonly styles = [
|
||||
css`
|
||||
:host {
|
||||
display: flex;
|
||||
|
||||
@@ -12,8 +12,10 @@ export default {
|
||||
required: 'Påkrævet',
|
||||
success: 'Succes',
|
||||
forgottenPassword: 'Glemt adgangskode?',
|
||||
forgottenPasswordInstruction: 'En e-mail vil blive sendt til den angivne adresse med et link til at nulstille din adgangskode',
|
||||
requestPasswordResetConfirmation: 'En e-mail med instruktioner for nulstilling af adgangskoden vil blive sendt til den angivne adresse, hvis det matcher vores optegnelser',
|
||||
forgottenPasswordInstruction:
|
||||
'En e-mail vil blive sendt til den angivne adresse med et link til at nulstille din adgangskode',
|
||||
requestPasswordResetConfirmation:
|
||||
'En e-mail med instruktioner for nulstilling af adgangskoden vil blive sendt til den angivne adresse, hvis det matcher vores optegnelser',
|
||||
setPasswordConfirmation: 'Din adgangskode er blevet opdateret',
|
||||
rememberMe: 'Husk mig',
|
||||
error: 'Fejl',
|
||||
@@ -26,8 +28,10 @@ export default {
|
||||
userLockedOut: 'Din konto er blevet låst. Prøv igen senere.',
|
||||
receivedErrorFromServer: 'Der skete en fejl på serveren',
|
||||
resetCodeExpired: 'Det link, du har klikket på, er ugyldigt eller udløbet',
|
||||
userInviteWelcomeMessage: 'Hej og velkommen til Umbraco! På bare 1 minut vil du være klar til at komme i gang, vi skal bare have dig til at oprette en adgangskode.',
|
||||
userInviteExpiredMessage: 'Velkommen til Umbraco! Desværre er din invitation udløbet. Kontakt din administrator og bed om at gensende invitationen.',
|
||||
userInviteWelcomeMessage:
|
||||
'Hej og velkommen til Umbraco! På bare 1 minut vil du være klar til at komme i gang, vi skal bare have dig til at oprette en adgangskode.',
|
||||
userInviteExpiredMessage:
|
||||
'Velkommen til Umbraco! Desværre er din invitation udløbet. Kontakt din administrator og bed om at gensende invitationen.',
|
||||
newPassword: 'Ny adgangskode',
|
||||
confirmNewPassword: 'Bekræft adgangskode',
|
||||
greeting0: 'Velkommen',
|
||||
@@ -45,8 +49,12 @@ export default {
|
||||
mfaInvalidCode: 'Forkert kode indtastet',
|
||||
signInWith: 'Log ind med {0}',
|
||||
returnToLogin: 'Tilbage til log ind',
|
||||
localLoginDisabled: 'Desværre er det ikke muligt at logge ind direkte. Det er blevet deaktiveret af en login-udbyder.',
|
||||
localLoginDisabled:
|
||||
'Desværre er det ikke muligt at logge ind direkte. Det er blevet deaktiveret af en login-udbyder.',
|
||||
friendlyGreeting: 'Hej!',
|
||||
requiredEmailValidationMessage: 'Udfyld venligst en e-mail',
|
||||
requiredUsernameValidationMessage: 'Udfyld venligst et brugernavn',
|
||||
requiredPasswordValidationMessage: 'Udfyld venligst en adgangskode',
|
||||
showPassword: 'Vis adgangskode',
|
||||
hidePassword: 'Skjul adgangskode',
|
||||
},
|
||||
|
||||
@@ -2,54 +2,6 @@ import type { UmbLocalizationDictionary } from '@umbraco-cms/backoffice/localiza
|
||||
|
||||
export default {
|
||||
auth: {
|
||||
continue: 'Continue',
|
||||
validate: 'Validate',
|
||||
login: 'Login',
|
||||
email: 'E-mail',
|
||||
username: 'Username',
|
||||
password: 'Password',
|
||||
submit: 'Submit',
|
||||
required: 'Required',
|
||||
success: 'Success',
|
||||
forgottenPassword: 'Forgotten password?',
|
||||
forgottenPasswordInstruction: 'An email will be sent to the address specified with a link to reset your password',
|
||||
requestPasswordResetConfirmation:
|
||||
'An email with password reset instructions will be sent to the specified address if it matched our records',
|
||||
setPasswordConfirmation: 'Your password has been updated',
|
||||
rememberMe: 'Remember me',
|
||||
error: 'Error',
|
||||
defaultError: 'An error occurred while processing your request.',
|
||||
errorInPasswordFormat:
|
||||
'The password must be at least {0} characters long and contain at least {1} special characters.',
|
||||
passwordMismatch: 'The confirmed password does not match the new password!',
|
||||
passwordMinLength: 'The password must be at least {0} characters long.',
|
||||
passwordIsBlank: 'The password cannot be blank.',
|
||||
userFailedLogin: "Oops! We couldn't log you in. Please check your credentials and try again.",
|
||||
userLockedOut: 'Your account has been locked out. Please try again later.',
|
||||
receivedErrorFromServer: 'Received an error from the server',
|
||||
resetCodeExpired: 'The link you have clicked on is invalid or has expired',
|
||||
userInviteWelcomeMessage:
|
||||
'Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password.',
|
||||
userInviteExpiredMessage:
|
||||
'Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.',
|
||||
newPassword: 'New password',
|
||||
confirmNewPassword: 'Confirm password',
|
||||
greeting0: 'Welcome',
|
||||
greeting1: 'Welcome',
|
||||
greeting2: 'Welcome',
|
||||
greeting3: 'Welcome',
|
||||
greeting4: 'Welcome',
|
||||
greeting5: 'Welcome',
|
||||
greeting6: 'Welcome',
|
||||
mfaTitle: 'One last step',
|
||||
mfaCodeInputHelp: 'Enter the code from your authenticator app',
|
||||
mfaText: 'You have enabled 2-factor authentication and must verify your identity.',
|
||||
mfaMultipleText: 'Please choose a 2-factor provider',
|
||||
mfaCodeInput: 'Verification code',
|
||||
mfaInvalidCode: 'Invalid code entered',
|
||||
signInWith: 'Sign in with {0}',
|
||||
returnToLogin: 'Return to login',
|
||||
localLoginDisabled: 'Unfortunately, direct login is not possible. It has been disabled by a provider.',
|
||||
friendlyGreeting: 'Hi there',
|
||||
},
|
||||
} satisfies UmbLocalizationDictionary;
|
||||
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
receivedErrorFromServer: 'Received an error from the server',
|
||||
resetCodeExpired: 'The link you have clicked on is invalid or has expired',
|
||||
userInviteWelcomeMessage:
|
||||
'Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password.',
|
||||
"Hello there and welcome to Umbraco! In just 1 minute you'll be good to go, we just need you to setup a password.",
|
||||
userInviteExpiredMessage:
|
||||
'Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.',
|
||||
newPassword: 'New password',
|
||||
@@ -51,6 +51,9 @@ export default {
|
||||
returnToLogin: 'Return to login',
|
||||
localLoginDisabled: 'Unfortunately, direct login is not possible. It has been disabled by a provider.',
|
||||
friendlyGreeting: 'Hello',
|
||||
requiredEmailValidationMessage: 'Please fill in an email',
|
||||
requiredUsernameValidationMessage: 'Please fill in a username',
|
||||
requiredPasswordValidationMessage: 'Please fill in a password',
|
||||
showPassword: 'Show password',
|
||||
hidePassword: 'Hide password',
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user