-
+
+

+
Welcome to your Umbraco installation
You're seeing this wonderful page because your website doesn't contain any published content yet.
diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
index e209e45cc4..f08ab2abe5 100644
--- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
+++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs
@@ -152,8 +152,9 @@ public class ContentSettings
internal const string StaticMacroErrors = "Inline";
internal const string StaticDisallowedUploadFiles = "ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,xamlx";
internal const bool StaticShowDeprecatedPropertyEditors = false;
- internal const string StaticLoginBackgroundImage = "assets/img/login.svg";
- internal const string StaticLoginLogoImage = "assets/img/application/umbraco_logo_white.svg";
+ internal const string StaticLoginBackgroundImage = "assets/img/login.jpg";
+ internal const string StaticLoginLogoImage = "assets/img/application/umbraco_logo_blue.svg";
+ internal const string StaticLoginLogoImageAlternative = "assets/img/application/umbraco_logo_blue.svg";
internal const bool StaticHideBackOfficeLogo = false;
internal const bool StaticDisableDeleteWhenReferenced = false;
internal const bool StaticDisableUnpublishWhenReferenced = false;
@@ -219,11 +220,21 @@ public class ContentSettings
public string LoginBackgroundImage { get; set; } = StaticLoginBackgroundImage;
///
- /// Gets or sets a value for the path to the login screen logo image.
+ /// Gets or sets a value for the path to the login screen logo image
+ /// shown on top of the background image set in .
///
+ ///
The alternative version of this logo can be found at .
[DefaultValue(StaticLoginLogoImage)]
public string LoginLogoImage { get; set; } = StaticLoginLogoImage;
+ ///
+ /// Gets or sets a value for the path to the login screen logo image when shown on top
+ /// of a light background (e.g. in mobile resolutions).
+ ///
+ ///
This is the alternative version to the regular logo found at .
+ [DefaultValue(StaticLoginLogoImageAlternative)]
+ public string LoginLogoImageAlternative { get; set; } = StaticLoginLogoImageAlternative;
+
///
/// Gets or sets a value indicating whether to hide the backoffice umbraco logo or not.
///
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml b/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml
index d7093a2ba5..3a715d6732 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/bs.xml
@@ -856,7 +856,7 @@
Korisničko ime
Vrijednost
Pogled
-
Dobrodošli...
+
Dobrodošli
Širina
Da
Mapa
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml b/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml
index 347f963e70..7332cd9300 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/cs.xml
@@ -714,7 +714,7 @@
Uživatelské jméno
Hodnota
Pohled
-
Vítejte...
+
Vítejte
Šířka
Ano
Složka
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml b/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml
index 86520b6d03..f387323d65 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/cy.xml
@@ -596,7 +596,7 @@
Ni ellir ailadeiladu'r mynegai hwn oherwydd nad yw wedi'i aseinio
IIndexPopulator
-
+
Cynnwys yn y mynegai
Ni ddarganfuwyd unrhyw ganlyniadau
Dangos %0% - %1% o %2% canlyniad(au) - Tudalen %3% o %4%
@@ -822,7 +822,7 @@
Enw defnyddiwr
Gwerth
Gwedd
-
Croeso...
+
Croeso
Lled
Ie
Ffolder
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml
index 3e01c0de9b..b72447aca7 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/da.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/da.xml
@@ -849,7 +849,7 @@
Brugernavn
Værdi
Vis
-
Velkommen...
+
Velkommen
Bredde
Ja
Mappe
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/de.xml b/src/Umbraco.Core/EmbeddedResources/Lang/de.xml
index 1dce3c2997..a500b0f385 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/de.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/de.xml
@@ -892,7 +892,7 @@
Validieren
Wert
Ansicht
-
Willkommen ...
+
Willkommen
Breite
Ja
Ordner
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
index 3c7bb7b5ca..c1aabf6f51 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml
@@ -869,7 +869,7 @@
Username
Value
View
-
Welcome...
+
Welcome
Width
Yes
Folder
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
index ed1b923d66..6b9c17eeb7 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml
@@ -899,7 +899,7 @@
Validate
Value
View
-
Welcome...
+
Welcome
Width
Yes
Folder
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/es.xml b/src/Umbraco.Core/EmbeddedResources/Lang/es.xml
index 25f183433a..54eac7cbf7 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/es.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/es.xml
@@ -556,7 +556,7 @@
Nombre de usuario
Valor
Ver
-
Bienvenido...
+
Bienvenido
Ancho
Si
Carpeta
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml
index a753a054fe..041572eea2 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/fr.xml
@@ -727,7 +727,7 @@
Nom d'utilisateur
Valeur
Voir
-
Bienvenue...
+
Bienvenue
Largeur
Oui
Dossier
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/he.xml b/src/Umbraco.Core/EmbeddedResources/Lang/he.xml
index f0427bbae1..615020ef3a 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/he.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/he.xml
@@ -328,7 +328,7 @@
שם משתמש
ערך
צפיה
-
ברוכים הבאים...
+
ברוכים הבאים
רוחב
כן
Reorder
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml
index 51847f3585..7f4b41e199 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/hr.xml
@@ -846,7 +846,7 @@
Korisničko ime
Vrijednost
Pogled
-
Dobrodošli...
+
Dobrodošli
Širina
Da
Mapa
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/it.xml b/src/Umbraco.Core/EmbeddedResources/Lang/it.xml
index 7c57a290fe..874f497491 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/it.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/it.xml
@@ -863,7 +863,7 @@
Valore
Vedi
-
Benvenuto...
+
Benvenuto
Larghezza
Si
Cartella
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/ja.xml b/src/Umbraco.Core/EmbeddedResources/Lang/ja.xml
index 9a94e7d924..f78576381e 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/ja.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/ja.xml
@@ -417,7 +417,7 @@
ユーザー名
値
ビュー
-
ようこそ...
+
ようこそ
幅
はい
フォルダー
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/ko.xml b/src/Umbraco.Core/EmbeddedResources/Lang/ko.xml
index 4aa0d4ad89..c2034dcaee 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/ko.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/ko.xml
@@ -328,7 +328,7 @@
사용자
값
보기
-
환영합니다...
+
환영합니다
너비
예
Reorder
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nb.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nb.xml
index c2784b3a9c..0191b85b17 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/nb.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/nb.xml
@@ -382,7 +382,7 @@
Brukernavn
Verdi
Visning
-
Velkommen...
+
Velkommen
Bredde
Ja
Mappe
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml
index 600a1724c1..9a32dfd999 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml
@@ -823,7 +823,7 @@
Gebruikersnaam
Waarde
Bekijk
-
Welkom...
+
Welkom
Breedte
Ja
Map
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/pl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/pl.xml
index 53aa716792..2f13780d04 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/pl.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/pl.xml
@@ -523,7 +523,7 @@
Nazwa użytkownika
Wartość
Widok
-
Witaj...
+
Witaj
Szerokość
Tak
Folder
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml b/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml
index 29ae77a2d8..40a5a8a59b 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/pt.xml
@@ -326,7 +326,7 @@
Usuário
Valor
Ver
-
Bem Vindo(a)...
+
Bem Vindo(a)
Largura
Sim
Reorder
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/ru.xml b/src/Umbraco.Core/EmbeddedResources/Lang/ru.xml
index b6d8a4a6b8..6ca52d46df 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/ru.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/ru.xml
@@ -574,7 +574,7 @@
Имя пользователя
Значение
Просмотр
-
Добро пожаловать...
+
Добро пожаловать
Ширина
Да
Пересортировать
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml
index 966089415a..e81175f5e4 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/sv.xml
@@ -114,7 +114,7 @@
%1% mer.]]>
-
%1% för många.]]>
+
%1% för många.]]>
%0%]]>
@@ -223,7 +223,7 @@
Publicerad (osparade ändringar)
Inga undernoder har lagts till
Inga ändringar har gjorts
-
Ej skapad
+
Ej skapad
Ja, ta bort
@@ -543,7 +543,7 @@
Användare
Användarnamn
Värde
-
Välkommen...
+
Välkommen
Bredd
Titta på
Ja
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml b/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml
index 1e1504f44a..84f1768168 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/tr.xml
@@ -739,7 +739,7 @@
Kullanıcı adı
Değer
Görüntüle
-
Hoş geldiniz ...
+
Hoş geldiniz
Genişlik
Evet
Klasör
diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/ua.xml b/src/Umbraco.Core/EmbeddedResources/Lang/ua.xml
index 599bd77426..aaa29e698a 100644
--- a/src/Umbraco.Core/EmbeddedResources/Lang/ua.xml
+++ b/src/Umbraco.Core/EmbeddedResources/Lang/ua.xml
@@ -574,7 +574,7 @@
Ім'я користувача
Значення
Перегляд
-
Ласкаво просимо...
+
Ласкаво просимо
Ширина
Так
Пересортувати
diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
index cc517ba178..c3ab60f059 100644
--- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
+++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs
@@ -269,6 +269,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
"imageFileTypes",
"loginBackgroundImage",
"loginLogoImage",
+ "loginLogoImageAlternative",
"canSendRequiredEmail",
"usernameIsEmail",
"hideBackofficeLogo",
@@ -619,6 +620,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers
{"allowPasswordReset", _securitySettings.AllowPasswordReset},
{"loginBackgroundImage", _contentSettings.LoginBackgroundImage},
{"loginLogoImage", _contentSettings.LoginLogoImage },
+ {"loginLogoImageAlternative", _contentSettings.LoginLogoImageAlternative },
{"hideBackofficeLogo", _contentSettings.HideBackOfficeLogo },
{"disableDeleteWhenReferenced", _contentSettings.DisableDeleteWhenReferenced },
{"disableUnpublishWhenReferenced", _contentSettings.DisableUnpublishWhenReferenced },
diff --git a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs
index 8117007986..32261eefef 100644
--- a/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs
+++ b/src/Umbraco.Web.BackOffice/Security/BackOfficeExternalLoginProviderOptions.cs
@@ -7,7 +7,7 @@ namespace Umbraco.Cms.Web.BackOffice.Security;
///
public class BackOfficeExternalLoginProviderOptions
{
- private string _buttonStyle = "btn-openid";
+ private string _buttonStyle = string.Empty;
public BackOfficeExternalLoginProviderOptions(
string buttonStyle,
@@ -30,8 +30,11 @@ public class BackOfficeExternalLoginProviderOptions
}
///
- /// Gets or sets the icon to use for the login button.
+ /// Gets or sets the style of the login button.
///
+ ///
+ /// The default look is an outlined button, which has been optimized for the login screen.
+ ///
[Obsolete("This is no longer used and will be removed in V15. Please set the ButtonLook and ButtonColor properties instead.")]
public string ButtonStyle
{
@@ -71,12 +74,18 @@ public class BackOfficeExternalLoginProviderOptions
/// Gets or sets the look to use for the login button.
/// See the UUI documentation for more details: https://uui.umbraco.com/?path=/story/uui-button--looks-and-colors.
///
+ ///
+ /// The default value is , which has been optimized for the login screen.
+ ///
public UuiButtonLook ButtonLook { get; set; } = UuiButtonLook.Outline;
///
/// Gets or sets the color to use for the login button.
/// See the UUI documentation for more details: https://uui.umbraco.com/?path=/story/uui-button--looks-and-colors.
///
+ ///
+ /// The default value is , which has been optimized for the login screen.
+ ///
public UuiButtonColor ButtonColor { get; set; } = UuiButtonColor.Default;
///
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png
index 2ddef4b53b..f134fd3006 100644
Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png and b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.png differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.svg b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.svg
new file mode 100644
index 0000000000..54cab15d6a
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo.svg
@@ -0,0 +1,17 @@
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@2x.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@2x.png
deleted file mode 100644
index c57755d62c..0000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@2x.png and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@3x.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@3x.png
deleted file mode 100644
index 7ed68fb3fc..0000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo@3x.png and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_black.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_black.png
deleted file mode 100644
index d3c6dc56c2..0000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_black.png and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_white.png b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_white.png
deleted file mode 100644
index 72b2fe470a..0000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_white.png and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_white.svg b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_white.svg
new file mode 100644
index 0000000000..309899db48
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/assets/img/application/logo_white.svg
@@ -0,0 +1,20 @@
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_blue.svg b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_blue.svg
new file mode 100644
index 0000000000..578bf592f6
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_blue.svg
@@ -0,0 +1,51 @@
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_blue.svg b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_blue.svg
index ce15dd3092..3fc811eaba 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_blue.svg
+++ b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_blue.svg
@@ -1,41 +1,39 @@
+
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_white.svg b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_white.svg
new file mode 100644
index 0000000000..7193f3ed0b
--- /dev/null
+++ b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_large_white.svg
@@ -0,0 +1,39 @@
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_white.svg b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_white.svg
index c0bdbdd40c..01f7260cd3 100644
--- a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_white.svg
+++ b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_white.svg
@@ -1,41 +1,51 @@
-
-
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logomark_white.svg b/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logomark_white.svg
deleted file mode 100644
index 08c2264337..0000000000
--- a/src/Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logomark_white.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/login.jpg b/src/Umbraco.Web.UI.Client/src/assets/img/login.jpg
index 06204510d0..3adc277159 100644
Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/login.jpg and b/src/Umbraco.Web.UI.Client/src/assets/img/login.jpg differ
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/login.svg b/src/Umbraco.Web.UI.Client/src/assets/img/login.svg
deleted file mode 100644
index 37499a996c..0000000000
--- a/src/Umbraco.Web.UI.Client/src/assets/img/login.svg
+++ /dev/null
@@ -1,996 +0,0 @@
-
-
-
diff --git a/src/Umbraco.Web.UI.Client/src/assets/img/logo.png b/src/Umbraco.Web.UI.Client/src/assets/img/logo.png
deleted file mode 100644
index ec59683c50..0000000000
Binary files a/src/Umbraco.Web.UI.Client/src/assets/img/logo.png and /dev/null differ
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js
index 71171c2a73..49a7346c76 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umbappheader.directive.js
@@ -12,9 +12,7 @@
scope.authenticated = null;
scope.user = null;
scope.avatar = [
- { value: "assets/img/application/logo.png" },
- { value: "assets/img/application/logo@2x.png" },
- { value: "assets/img/application/logo@3x.png" }
+ { value: "assets/img/application/logo.svg" }
];
scope.hideBackofficeLogo = Umbraco.Sys.ServerVariables.umbracoSettings.hideBackofficeLogo;
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js
index e2c6d87e19..836011c4f6 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/application/umblogin.directive.js
@@ -20,6 +20,7 @@
vm.backgroundImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginBackgroundImage;
vm.logoImage = Umbraco.Sys.ServerVariables.umbracoSettings.loginLogoImage;
+ vm.logoImageAlternative = Umbraco.Sys.ServerVariables.umbracoSettings.loginLogoImageAlternative;
vm.allowPasswordReset = Umbraco.Sys.ServerVariables.umbracoSettings.canSendRequiredEmail && Umbraco.Sys.ServerVariables.umbracoSettings.allowPasswordReset;
vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js
index 9ad475ad85..5e97a281f9 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbavatar.directive.js
@@ -30,9 +30,7 @@ Use this directive to render an avatar.
var vm = this;
vm.avatar = [
- { value: "assets/logo.png" },
- { value: "assets/logo@2x.png" },
- { value: "assets/logo@3x.png" }
+ { value: "assets/img/application/logo.svg" }
];
}
@@ -55,7 +53,7 @@ Use this directive to render an avatar.
function AvatarDirective(localizationService) {
function link(scope, element, attrs, ctrl) {
-
+
var eventBindings = [];
scope.initials = "";
scope.avatarAlt = "";
diff --git a/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less b/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less
index 1bf9aab829..89fbd269c0 100644
--- a/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less
+++ b/src/Umbraco.Web.UI.Client/src/less/pages/nonodes.less
@@ -296,10 +296,6 @@ section article>div {
}
section .logo {
- background-image: url(../img/logo.png);
- background-repeat: no-repeat;
- width: 91px;
- height: 91px;
margin: 0 auto;
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html
index fae3543429..29294a08c4 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-app-header.html
@@ -41,7 +41,7 @@
ng-focus="keepLogoModal()"
ng-blur="hideLogoModal()"
ng-if="logoModal.show">
-
@@ -122,7 +122,6 @@
color="secondary"
name="{{user.name}}"
img-src="{{avatar[0].value}}"
- img-srcset="{{avatar[1].value}} 2x, {{avatar[2].value}} 3x"
>
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html
index 242b4eab9c..952a40fa5c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html
+++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-login.html
@@ -1,8 +1,9 @@
diff --git a/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html b/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html
index 0f13862628..1bd82a2aa5 100644
--- a/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html
+++ b/src/Umbraco.Web.UI.Client/src/views/errors/BootFailed.html
@@ -69,7 +69,7 @@
-
-
-
-
-
-
- Umbraco
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ if (import.meta.env.DEV) {
+ await startMockServiceWorker();
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Web.UI.Login/public/login.jpg b/src/Umbraco.Web.UI.Login/public/login.jpg
new file mode 120000
index 0000000000..6dbf39e433
--- /dev/null
+++ b/src/Umbraco.Web.UI.Login/public/login.jpg
@@ -0,0 +1 @@
+../../Umbraco.Web.UI.Client/src/assets/img/login.jpg
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Login/public/login.svg b/src/Umbraco.Web.UI.Login/public/login.svg
deleted file mode 100644
index 37499a996c..0000000000
--- a/src/Umbraco.Web.UI.Login/public/login.svg
+++ /dev/null
@@ -1,996 +0,0 @@
-
-
-
diff --git a/src/Umbraco.Web.UI.Login/public/logo_dark.svg b/src/Umbraco.Web.UI.Login/public/logo_dark.svg
new file mode 120000
index 0000000000..f4ffd8fbdc
--- /dev/null
+++ b/src/Umbraco.Web.UI.Login/public/logo_dark.svg
@@ -0,0 +1 @@
+../../Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_blue.svg
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Login/public/logo_light.svg b/src/Umbraco.Web.UI.Login/public/logo_light.svg
new file mode 120000
index 0000000000..8d8d636018
--- /dev/null
+++ b/src/Umbraco.Web.UI.Login/public/logo_light.svg
@@ -0,0 +1 @@
+../../Umbraco.Web.UI.Client/src/assets/img/application/umbraco_logo_white.svg
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Login/public/umbraco_logo_white.svg b/src/Umbraco.Web.UI.Login/public/umbraco_logo_white.svg
deleted file mode 100644
index 3de8e82ed3..0000000000
--- a/src/Umbraco.Web.UI.Login/public/umbraco_logo_white.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Login/public/umbraco_logomark_white.svg b/src/Umbraco.Web.UI.Login/public/umbraco_logomark_white.svg
deleted file mode 100644
index 9372e25d3e..0000000000
--- a/src/Umbraco.Web.UI.Login/public/umbraco_logomark_white.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/Umbraco.Web.UI.Login/src/auth-styles.css b/src/Umbraco.Web.UI.Login/src/auth-styles.css
index 457c7b2c26..8e5b5efd53 100644
--- a/src/Umbraco.Web.UI.Login/src/auth-styles.css
+++ b/src/Umbraco.Web.UI.Login/src/auth-styles.css
@@ -1,10 +1,6 @@
-body {
- margin: 0;
- padding: 0;
-}
#umb-login-form umb-login-input {
width: 100%;
- height: 38px;
+ height: var(--input-height);
box-sizing: border-box;
display: block;
border: 1px solid var(--uui-color-border);
@@ -12,23 +8,29 @@ body {
outline: none;
background-color: var(--uui-color-surface);
}
+
#umb-login-form umb-login-input input {
width: 100%;
height: 100%;
display: block;
box-sizing: border-box;
border: none;
- background: none;
outline: none;
- padding: var(--uui-size-1, 3px) var(--uui-size-space-3, 9px);
+ background: none;
+ padding: var(--uui-size-1, 3px) var(--uui-size-space-4, 9px);
+ font-size: inherit;
}
+
#umb-login-form uui-form-layout-item {
margin-top: var(--uui-size-space-4);
margin-bottom: var(--uui-size-space-4);
}
+
#umb-login-form umb-login-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 umb-login-input:hover:not(:focus-within) {
border-color: var(--uui-input-border-color-hover, var(--uui-color-border-standalone, #c2c2c2));
}
diff --git a/src/Umbraco.Web.UI.Login/src/auth.element.ts b/src/Umbraco.Web.UI.Login/src/auth.element.ts
index 7bf9c1fa48..c81c5a3e31 100644
--- a/src/Umbraco.Web.UI.Login/src/auth.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/auth.element.ts
@@ -7,11 +7,19 @@ import { umbAuthContext } from './context/auth.context.js';
import { umbLocalizationContext } from './external/localization/localization-context.js';
import { UmbLocalizeElement } from './external/localization/localize.element.js';
import type { UmbLoginInputElement } from './components/login-input.element.js';
-import { InputType, UUIFormLayoutItemElement, UUILabelElement } from '@umbraco-ui/uui';
+import type { InputType, UUIFormLayoutItemElement, UUILabelElement } from '@umbraco-ui/uui';
import authStyles from './auth-styles.css?inline';
-const createInput = (opts: {id: string, type: InputType, name: string, autocomplete: AutoFill, requiredMessage: string, label: string, inputmode: string}) => {
+const createInput = (opts: {
+ id: string;
+ type: InputType;
+ name: string;
+ autocomplete: AutoFill;
+ requiredMessage: string;
+ label: string;
+ inputmode: string;
+}) => {
const input = document.createElement('umb-login-input');
input.type = opts.type;
input.name = opts.name;
@@ -26,7 +34,7 @@ const createInput = (opts: {id: string, type: InputType, name: string, autocompl
return input;
};
-const createLabel = (opts: {forId: string, localizeAlias: string}) => {
+const createLabel = (opts: { forId: string; localizeAlias: string }) => {
const label = document.createElement('uui-label');
const umbLocalize = document.createElement('umb-localize') as UmbLocalizeElement;
umbLocalize.key = opts.localizeAlias;
@@ -65,27 +73,30 @@ export default class UmbAuthElement extends LitElement {
*
* @attr disable-local-login
*/
- @property({ type: Boolean, attribute: 'disable-local-login' })
+ @property({type: Boolean, attribute: 'disable-local-login'})
set disableLocalLogin(value: boolean) {
umbAuthContext.disableLocalLogin = value;
}
- @property({ attribute: 'background-image' })
+ @property({attribute: 'background-image'})
backgroundImage?: string;
- @property({ attribute: 'logo-image' })
+ @property({attribute: 'logo-image'})
logoImage?: string;
- @property({ type: Boolean, attribute: 'username-is-email' })
+ @property({attribute: 'logo-image-alternative'})
+ logoImageAlternative?: string;
+
+ @property({type: Boolean, attribute: 'username-is-email'})
usernameIsEmail = false;
- @property({ type: Boolean, attribute: 'allow-password-reset' })
+ @property({type: Boolean, attribute: 'allow-password-reset'})
allowPasswordReset = false;
- @property({ type: Boolean, attribute: 'allow-user-invite' })
+ @property({type: Boolean, attribute: 'allow-user-invite'})
allowUserInvite = false;
- @property({ type: String, attribute: 'return-url' })
+ @property({attribute: 'return-url'})
set returnPath(value: string) {
umbAuthContext.returnPath = value;
}
@@ -140,10 +151,9 @@ export default class UmbAuthElement extends LitElement {
* @private
*/
async #initializeForm() {
- const labelUsername =
- this.usernameIsEmail
- ? await umbLocalizationContext.localize('general_username', undefined, 'Username')
- : await umbLocalizationContext.localize('general_email', undefined, 'Email');
+ const labelUsername = this.usernameIsEmail
+ ? await umbLocalizationContext.localize('general_username', undefined, 'Username')
+ : await umbLocalizationContext.localize('general_email', undefined, 'Email');
const labelPassword = await umbLocalizationContext.localize('general_password', undefined, 'Password');
const requiredMessage = await umbLocalizationContext.localize('general_required', undefined, 'Required');
@@ -154,7 +164,7 @@ export default class UmbAuthElement extends LitElement {
autocomplete: 'username',
requiredMessage,
label: labelUsername,
- inputmode: this.usernameIsEmail ? 'email' : ''
+ inputmode: this.usernameIsEmail ? 'email' : '',
});
this._passwordInput = createInput({
id: 'password-input',
@@ -163,10 +173,13 @@ export default class UmbAuthElement extends LitElement {
autocomplete: 'current-password',
requiredMessage,
label: labelPassword,
- inputmode: ''
+ inputmode: '',
});
- this._usernameLabel = createLabel({ forId: 'username-input', localizeAlias: this.usernameIsEmail ? 'general_email' : 'user_username' });
- this._passwordLabel = createLabel({ forId: 'password-input', localizeAlias: 'user_password' });
+ this._usernameLabel = createLabel({
+ forId: 'username-input',
+ localizeAlias: this.usernameIsEmail ? 'general_email' : 'general_username',
+ });
+ this._passwordLabel = createLabel({forId: 'password-input', localizeAlias: 'user_password'});
this._usernameLayoutItem = createFormLayoutItem(this._usernameLabel, this._usernameInput);
this._passwordLayoutItem = createFormLayoutItem(this._passwordLabel, this._passwordInput);
@@ -178,12 +191,13 @@ export default class UmbAuthElement extends LitElement {
render() {
return html`
-
- ${this._renderFlowAndStatus()}
-
- `;
+
+ ${this._renderFlowAndStatus()}
+
+ `;
}
private _renderFlowAndStatus() {
@@ -192,21 +206,31 @@ export default class UmbAuthElement extends LitElement {
const status = searchParams.get('status');
if (status === 'resetCodeExpired') {
- return html`
- `;
+ return html`
+
+ `;
}
if (flow === 'invite-user' && status === 'false') {
- return html`
- `;
+ return html`
+
+ `;
}
// validate
@@ -218,22 +242,27 @@ export default class UmbAuthElement extends LitElement {
switch (flow) {
case 'mfa':
- return html``;
+ return html`
+ `;
case 'reset':
- return html``;
+ return html`
+ `;
case 'reset-password':
- return html``;
+ return html`
+ `;
case 'invite-user':
- return html``;
+ return html`
+ `;
default:
- return html`
-
-
-
- `;
+ return html`
+
+
+
+
+ `;
}
}
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/back-to-login-button.element.ts b/src/Umbraco.Web.UI.Login/src/components/back-to-login-button.element.ts
index adf773f66a..701be018e8 100644
--- a/src/Umbraco.Web.UI.Login/src/components/back-to-login-button.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/back-to-login-button.element.ts
@@ -17,7 +17,7 @@ export default class UmbBackToLoginButtonElement extends LitElement {
}
#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' } }));
}
static styles: CSSResultGroup = [
diff --git a/src/Umbraco.Web.UI.Login/src/components/external-login-provider.element.ts b/src/Umbraco.Web.UI.Login/src/components/external-login-provider.element.ts
index 8a464bf948..6f0570a91b 100644
--- a/src/Umbraco.Web.UI.Login/src/components/external-login-provider.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/external-login-provider.element.ts
@@ -7,9 +7,9 @@ import { loadCustomView, renderCustomView } from '../utils/load-custom-view.func
import { umbLocalizationContext } from '../external/localization/localization-context.js';
type ExternalLoginCustomViewElement = HTMLElement & {
- displayName: string;
- providerName: string;
- externalLoginUrl: string;
+ displayName: string;
+ providerName: string;
+ externalLoginUrl: string;
};
/**
@@ -19,188 +19,197 @@ type ExternalLoginCustomViewElement = HTMLElement & {
*/
@customElement('umb-external-login-provider')
export class UmbExternalLoginProviderElement extends LitElement {
- /**
- * Gets or sets the path to the module that should be loaded as the custom view.
- * The module should export a default class that extends HTMLElement.
- *
- * Setting this property will cause the default view to be hidden and the custom view to be loaded.
- * The icon, button look and button color will be ignored.
- *
- * @example App_Plugins/MyPackage/MyCustomLoginView.js
- * @attr custom-view
- */
- @property({ attribute: 'custom-view' })
- customView?: string;
+ /**
+ * Gets or sets the path to the module that should be loaded as the custom view.
+ * The module should export a default class that extends HTMLElement.
+ *
+ * Setting this property will cause the default view to be hidden and the custom view to be loaded.
+ * The icon, button look and button color will be ignored.
+ *
+ * @example App_Plugins/MyPackage/MyCustomLoginView.js
+ * @attr custom-view
+ */
+ @property({attribute: 'custom-view'})
+ customView?: string;
- /**
- * Gets or sets the display name of the provider.
- *
- * @attr display-name
- * @example Google
- */
- @property({ attribute: 'display-name' })
- displayName = '';
+ /**
+ * Gets or sets the display name of the provider.
+ *
+ * @attr display-name
+ * @example Google
+ */
+ @property({attribute: 'display-name'})
+ displayName = '';
- /**
- * Gets or sets the name of the provider (otherwise known as authentication type).
- *
- * @attr provider-name
- * @example Umbraco.Google
- */
- @property({ attribute: 'provider-name' })
- providerName = '';
+ /**
+ * Gets or sets the name of the provider (otherwise known as authentication type).
+ *
+ * @attr provider-name
+ * @example Umbraco.Google
+ */
+ @property({attribute: 'provider-name'})
+ providerName = '';
- /**
- * Gets or sets the url to the external login provider.
- *
- * @attr external-login-url
- * @example /umbraco/ExternalLogin
- */
- @property({ attribute: 'external-login-url' })
- get externalLoginUrl() {
- return this.#externalLoginUrl;
- }
- set externalLoginUrl(value: string) {
- const tempUrl = new URL(value, window.location.origin);
- const searchParams = new URLSearchParams(tempUrl.search);
- tempUrl.searchParams.append('redirectUrl', decodeURIComponent(searchParams.get('returnPath') ?? ''));
- this.#externalLoginUrl = tempUrl.pathname + tempUrl.search;
- }
+ /**
+ * Gets or sets the url to the external login provider.
+ *
+ * @attr external-login-url
+ * @example /umbraco/ExternalLogin
+ */
+ @property({attribute: 'external-login-url'})
+ set externalLoginUrl(value: string) {
+ const tempUrl = new URL(value, window.location.origin);
+ const searchParams = new URLSearchParams(tempUrl.search);
+ tempUrl.searchParams.append('redirectUrl', decodeURIComponent(searchParams.get('returnPath') ?? ''));
+ this.#externalLoginUrl = tempUrl.pathname + tempUrl.search;
+ }
- /**
- * Gets or sets the icon to display next to the provider name.
- * This should be the name of an icon in the Umbraco Backoffice icon set.
- *
- * @attr icon
- * @example icon-google-fill
- * @default icon-lock
- */
- @property({ attribute: 'icon' })
- icon = 'icon-lock';
+ get externalLoginUrl() {
+ return this.#externalLoginUrl;
+ }
- /**
- * Gets or sets the look of the underlying uui-button.
- *
- * @attr button-look
- * @example outline
- * @default outline
- * @see https://uui.umbraco.com/?path=/story/uui-button--looks-and-colors
- */
- @property({ attribute: 'button-look' })
- buttonLook: InterfaceLook = 'outline';
- /**
- * Gets or sets the color of the underlying uui-button.
- *
- * @attr button-color
- * @example danger
- * @default default
- * @see https://uui.umbraco.com/?path=/story/uui-button--looks-and-colors
- */
- @property({ attribute: 'button-color' })
- buttonColor: InterfaceColor = 'default';
+ /**
+ * Gets or sets the icon to display next to the provider name.
+ * This should be the name of an icon in the Umbraco Backoffice icon set.
+ *
+ * @attr icon
+ * @example icon-google-fill
+ * @default icon-lock
+ */
+ @property({attribute: 'icon'})
+ icon = 'icon-lock';
- #externalLoginUrl = '';
+ /**
+ * Gets or sets the look of the underlying uui-button.
+ *
+ * @attr button-look
+ * @example outline
+ * @default outline
+ * @see https://uui.umbraco.com/?path=/story/uui-button--looks-and-colors
+ */
+ @property({attribute: 'button-look'})
+ buttonLook: InterfaceLook = 'outline';
- protected render() {
- return this.customView
- ? until(this.renderCustomView(), html``)
- : this.renderDefaultView();
- }
+ /**
+ * Gets or sets the color of the underlying uui-button.
+ *
+ * @attr button-color
+ * @example danger
+ * @default default
+ * @see https://uui.umbraco.com/?path=/story/uui-button--looks-and-colors
+ */
+ @property({attribute: 'button-color'})
+ buttonColor: InterfaceColor = 'default';
- protected renderDefaultView() {
- return html`
-
+ `;
+ }
- protected async renderCustomView() {
- try {
- if (!this.customView) return;
+ protected async renderCustomView() {
+ try {
+ if (!this.customView) return;
- const customView = await loadCustomView(this.customView);
+ const customView = await loadCustomView(this.customView);
- if (typeof customView === 'object') {
- customView.displayName = this.displayName;
- customView.providerName = this.providerName;
- customView.externalLoginUrl = this.externalLoginUrl;
- }
+ if (typeof customView === 'object') {
+ customView.displayName = this.displayName;
+ customView.providerName = this.providerName;
+ customView.externalLoginUrl = this.externalLoginUrl;
+ }
- return renderCustomView(customView);
- } catch (error: unknown) {
- console.group('[External login] Failed to load custom view');
- console.log('Provider name', this.providerName);
- console.log('Element reference', this);
- console.log('Custom view', this.customView);
- console.error('Failed to load custom view:', error);
- console.groupEnd();
- }
- }
+ return renderCustomView(customView);
+ } catch (error: unknown) {
+ console.group('[External login] Failed to load custom view');
+ console.log('Provider name', this.providerName);
+ console.log('Element reference', this);
+ console.log('Custom view', this.customView);
+ console.error('Failed to load custom view:', error);
+ console.groupEnd();
+ }
+ }
- static styles: CSSResultGroup = [
- css`
- #defaultView uui-button {
- width: 100%;
- --uui-button-padding-top-factor: 1.5;
- --uui-button-padding-bottom-factor: 1.5;
- }
- #defaultView uui-button div {
- /* TODO: Remove this when uui-button has setting for aligning content */
- position: absolute;
- left: 9px;
- margin: auto;
- text-align: left;
- top: 50%;
- transform: translateY(-50%);
- }
- #defaultView button {
- font-size: var(--uui-button-font-size);
- border: 1px solid var(--uui-color-border);
- border-radius: var(--uui-border-radius);
- width: 100%;
- padding: 9px;
- text-align: left;
- background-color: var(--uui-color-surface);
- cursor: pointer;
- display: flex;
- align-items: center;
- gap: var(--uui-size-space-2);
- box-sizing: border-box;
+ static styles: CSSResultGroup = [
+ css`
+ #defaultView uui-button {
+ width: 100%;
+ --uui-button-font-weight: 400;
+ }
- line-height: 1.1; /* makes the text vertically centered */
- color: var(--uui-color-interactive);
- }
+ #defaultView uui-button div {
+ /* TODO: Remove this when uui-button has setting for aligning content */
+ position: absolute;
+ top: 50%;
+ left: 0;
+ margin: auto;
+ transform: translateY(-50%);
+ text-align: left;
+ padding-left: 15px;
+ }
- #defaultView button:hover {
- color: var(--uui-color-interactive-emphasis);
- border-color: var(--uui-color-border-standalone);
- }
- `,
- ];
+ #defaultView uui-icon {
+ color: #00000080;
+ padding-right: 2px;
+ }
+
+ #defaultView button {
+ font-size: var(--uui-button-font-size);
+ border: 1px solid var(--uui-color-border);
+ border-radius: var(--uui-button-border-radius);
+ width: 100%;
+ padding: 9px;
+ text-align: left;
+ background-color: var(--uui-color-surface);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: var(--uui-size-space-2);
+ box-sizing: border-box;
+
+ line-height: 1.1; /* makes the text vertically centered */
+ color: var(--uui-color-interactive);
+ }
+
+ #defaultView button:hover {
+ color: var(--uui-color-interactive-emphasis);
+ border-color: var(--uui-color-border-standalone);
+ }
+ `,
+ ];
}
declare global {
- interface HTMLElementTagNameMap {
- 'umb-external-login-provider': UmbExternalLoginProviderElement;
- }
+ interface HTMLElementTagNameMap {
+ 'umb-external-login-provider': UmbExternalLoginProviderElement;
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/layouts/auth-layout.element.ts b/src/Umbraco.Web.UI.Login/src/components/layouts/auth-layout.element.ts
index a76e032ffa..0fac580fdc 100644
--- a/src/Umbraco.Web.UI.Login/src/components/layouts/auth-layout.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/layouts/auth-layout.element.ts
@@ -1,80 +1,192 @@
-import { css, CSSResultGroup, html, LitElement, nothing } from 'lit';
+import { css, CSSResultGroup, html, LitElement, nothing, PropertyValueMap } from 'lit';
import { customElement, property } from 'lit/decorators.js';
-import { styleMap } from 'lit/directives/style-map.js';
+import { when } from 'lit/directives/when.js';
@customElement('umb-auth-layout')
export class UmbAuthLayoutElement extends LitElement {
- @property({ attribute: 'background-image' })
- backgroundImage?: string;
+ @property({ attribute: 'background-image' })
+ backgroundImage?: string;
- @property({ attribute: 'logo-image' })
- logoImage?: string;
+ @property({ attribute: 'logo-image' })
+ logoImage?: string;
- render() {
- return html`
-
+ @property({ attribute: 'logo-image-alternative' })
+ logoImageAlternative?: string;
- ${this.logoImage ? html`
` : nothing}
+ protected updated(_changedProperties: PropertyValueMap | Map): void {
+ super.updated(_changedProperties);
-
- `;
- }
+ if (_changedProperties.has('backgroundImage')) {
+ this.style.setProperty('--logo-alternative-display', this.backgroundImage ? 'none' : 'unset');
+ this.style.setProperty('--image', `url('${this.backgroundImage}')`);
+ }
+ }
- static styles: CSSResultGroup = [
- css`
- #background {
- position: fixed;
- overflow: hidden;
- background-position: 50%;
- background-repeat: no-repeat;
- background-size: cover;
- width: 100vw;
- height: 100vh;
- }
+ #renderImageContainer() {
+ if (!this.backgroundImage) return nothing;
- #logo {
- position: fixed;
- top: var(--uui-size-space-5);
- left: var(--uui-size-space-5);
- height: 30px;
- }
+ return html`
+
+
+
+
- #logo img {
- height: 100%;
- }
+ ${when(
+ this.logoImage,
+ () => html`

`
+ )}
+
+
+ `;
+ }
- #container {
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- width: 100vw;
- height: 100vh;
- }
+ #renderContent() {
+ return html`
+
+ `;
+ }
- #box {
- width: 500px;
- padding: var(--uui-size-layout-3);
- background-color: var(--uui-color-surface-alt);
- box-sizing: border-box;
- box-shadow: var(--uui-shadow-depth-5);
- border-radius: calc(var(--uui-border-radius) * 2);
- }
+ render() {
+ return html`
+
+ ${this.#renderImageContainer()} ${this.#renderContent()}
+
+ ${when(
+ this.logoImageAlternative,
+ () => html`
`
+ )}
+ `;
+ }
- #email,
- #password {
- width: 100%;
- }
- `,
- ];
+ static styles: CSSResultGroup = [
+ css`
+ :host {
+ --uui-color-interactive: #283a97;
+ --uui-button-border-radius: 45px;
+ --uui-color-default: var(--uui-color-interactive);
+ --uui-button-height: 42px;
+ --uui-select-height: 38px;
+
+ --input-height: 40px;
+ --header-font-size: 3rem;
+ --header-secondary-font-size: 2.4rem;
+ display: block;
+ background-color: #f4f4f4;
+ }
+
+ #main-no-image,
+ #main {
+ max-width: 1920px;
+ display: flex;
+ height: 100vh;
+ padding: 8px;
+ box-sizing: border-box;
+ margin: 0 auto;
+ }
+
+ #image-container {
+ display: none;
+ width: 100%;
+ }
+
+ #content-container {
+ display: flex;
+ width: 100%;
+ box-sizing: border-box;
+ overflow: auto;
+ }
+
+ #content {
+ max-width: 360px;
+ margin: auto;
+ width: 100%;
+ }
+
+ #image {
+ background-image: var(--image);
+ background-position: 50%;
+ background-repeat: no-repeat;
+ background-size: cover;
+ width: 100%;
+ height: 100%;
+ border-radius: 38px;
+ position: relative;
+ overflow: hidden;
+ }
+
+ #image svg {
+ position: absolute;
+ width: 45%;
+ height: fit-content;
+ }
+
+ #curve-top {
+ top: 0;
+ right: 0;
+ }
+
+ #curve-bottom {
+ bottom: 0;
+ left: 0;
+ }
+
+ #logo-on-image,
+ #logo-on-background {
+ position: absolute;
+ top: 24px;
+ left: 24px;
+ height: 55px;
+ }
+
+ @media only screen and (min-width: 900px) {
+ :host {
+ --header-font-size: 4rem;
+ }
+
+ #main {
+ padding: 32px;
+ padding-right: 0;
+ }
+
+ #image-container {
+ display: block;
+ }
+
+ #content-container {
+ display: flex;
+ padding: 16px;
+ }
+
+ #logo-on-background {
+ display: var(--logo-alternative-display);
+ }
+ }
+ `,
+ ];
}
declare global {
- interface HTMLElementTagNameMap {
- 'umb-auth-layout': UmbAuthLayoutElement;
- }
+ interface HTMLElementTagNameMap {
+ 'umb-auth-layout': UmbAuthLayoutElement;
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/layouts/confirmation-layout.element.ts b/src/Umbraco.Web.UI.Login/src/components/layouts/confirmation-layout.element.ts
index 4f8b3bed30..488a963698 100644
--- a/src/Umbraco.Web.UI.Login/src/components/layouts/confirmation-layout.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/layouts/confirmation-layout.element.ts
@@ -3,59 +3,65 @@ import { customElement, property } from 'lit/decorators.js';
@customElement('umb-confirmation-layout')
export default class UmbConfirmationLayoutElement extends LitElement {
- @property({ type: String })
- header = '';
+ @property({ type: String })
+ header = '';
- @property({ type: String })
- message = '';
+ @property({ type: String })
+ message = '';
- render() {
- return html`
-
+ render() {
+ return html`
+
-
+
-
- `;
- }
+
+ `;
+ }
- static styles: CSSResultGroup = [
- css`
- :host {
- display: flex;
- flex-direction: column;
- gap: var(--uui-size-layout-1);
- }
- #header {
- text-align: center;
- display: flex;
- flex-direction: column;
- gap: var(--uui-size-space-5);
- }
- #header span {
- color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */
- font-size: 14px;
- }
- #header h2 {
- margin: 0px;
- font-weight: bold;
- font-size: 1.4rem;
- }
- uui-button {
- width: 100%;
- margin-top: var(--uui-size-space-5);
- --uui-button-padding-top-factor: 1.5;
- --uui-button-padding-bottom-factor: 1.5;
- }
- `,
- ];
+ static styles: CSSResultGroup = [
+ css`
+ :host {
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-layout-1);
+ }
+
+ #header {
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-space-5);
+ }
+
+ #header span {
+ color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */
+ font-size: 14px;
+ }
+
+ #header h1 {
+ margin: 0;
+ font-weight: 400;
+ font-size: var(--header-secondary-font-size);
+ color: var(--uui-color-interactive);
+ line-height: 1.2;
+ }
+
+ uui-button {
+ width: 100%;
+ margin-top: var(--uui-size-space-5);
+ --uui-button-padding-top-factor: 1.5;
+ --uui-button-padding-bottom-factor: 1.5;
+ }
+ `,
+ ];
}
declare global {
- interface HTMLElementTagNameMap {
- 'umb-confirmation-layout': UmbConfirmationLayoutElement;
- }
+ interface HTMLElementTagNameMap {
+ 'umb-confirmation-layout': UmbConfirmationLayoutElement;
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/layouts/error-layout.element.ts b/src/Umbraco.Web.UI.Login/src/components/layouts/error-layout.element.ts
index 37fe931e9a..4f1da31fc0 100644
--- a/src/Umbraco.Web.UI.Login/src/components/layouts/error-layout.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/layouts/error-layout.element.ts
@@ -3,57 +3,63 @@ import { customElement, property } from 'lit/decorators.js';
@customElement('umb-error-layout')
export default class UmbErrorLayoutElement extends LitElement {
- @property({ type: String })
- header = '';
+ @property({ type: String })
+ header = '';
- @property({ type: String })
- message = '';
+ @property({ type: String })
+ message = '';
- render() {
- return html`
-
-
-
- `;
- }
+ render() {
+ return html`
+
+
+
+ `;
+ }
- static styles: CSSResultGroup = [
- css`
- :host {
- display: flex;
- flex-direction: column;
- gap: var(--uui-size-layout-1);
- }
- #header {
- text-align: center;
- display: flex;
- flex-direction: column;
- gap: var(--uui-size-space-5);
- }
- #header span {
- color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */
- font-size: 14px;
- }
- #header h2 {
- margin: 0;
- font-weight: bold;
- font-size: 1.4rem;
- }
- ::slotted(uui-button) {
- width: 100%;
- margin-top: var(--uui-size-space-5);
- --uui-button-padding-top-factor: 1.5;
- --uui-button-padding-bottom-factor: 1.5;
- }
- `,
- ];
+ static styles: CSSResultGroup = [
+ css`
+ :host {
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-layout-1);
+ }
+
+ #header {
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-space-5);
+ }
+
+ #header span {
+ color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */
+ font-size: 14px;
+ }
+
+ #header h1 {
+ margin: 0;
+ font-weight: 400;
+ font-size: var(--header-secondary-font-size);
+ color: var(--uui-color-interactive);
+ line-height: 1.2;
+ }
+
+ ::slotted(uui-button) {
+ width: 100%;
+ margin-top: var(--uui-size-space-5);
+ --uui-button-padding-top-factor: 1.5;
+ --uui-button-padding-bottom-factor: 1.5;
+ }
+ `,
+ ];
}
declare global {
- interface HTMLElementTagNameMap {
- 'umb-error-layout': UmbErrorLayoutElement;
- }
+ interface HTMLElementTagNameMap {
+ 'umb-error-layout': UmbErrorLayoutElement;
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/layouts/external-login-providers-layout.element.ts b/src/Umbraco.Web.UI.Login/src/components/layouts/external-login-providers-layout.element.ts
index ac28eaf6ec..356bd4a618 100644
--- a/src/Umbraco.Web.UI.Login/src/components/layouts/external-login-providers-layout.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/layouts/external-login-providers-layout.element.ts
@@ -1,77 +1,92 @@
import { css, CSSResultGroup, html, LitElement, nothing } from 'lit';
import { customElement, property, queryAssignedElements } from 'lit/decorators.js';
+import { until } from 'lit/directives/until.js';
+import { umbLocalizationContext } from '../../external/localization/localization-context.js';
@customElement('umb-external-login-providers-layout')
export class UmbExternalLoginProvidersLayoutElement extends LitElement {
- @property({ type: Boolean, attribute: 'divider' })
- showDivider = true;
+ @property({ type: Boolean, attribute: 'divider' })
+ showDivider = true;
- @queryAssignedElements({ flatten: true })
- protected slottedElements?: HTMLElement[];
+ @queryAssignedElements({ flatten: true })
+ protected slottedElements?: HTMLElement[];
- firstUpdated() {
- !!this.slottedElements?.length ? this.toggleAttribute('empty', false) : this.toggleAttribute('empty', true);
- }
+ firstUpdated() {
+ !!this.slottedElements?.length ? this.toggleAttribute('empty', false) : this.toggleAttribute('empty', true);
+ }
- render() {
- return html`
- ${this.showDivider ? html` Or
` : nothing}
-
-
-
- `;
- }
+ render() {
+ return html`
+ ${this.showDivider
+ ? html`
+
+ ${until(umbLocalizationContext.localize('general_or', undefined, 'or').then(str => str.toLocaleLowerCase()))}
+
+ `
+ : nothing}
+
+
+
+ `;
+ }
- static styles: CSSResultGroup = [
- css`
- :host {
- margin-top: 16px;
- display: flex;
- flex-direction: column;
- }
+ static styles: CSSResultGroup = [
+ css`
+ :host {
+ margin-top: 16px;
+ display: flex;
+ flex-direction: column;
+ }
- :host([empty]) {
- display: none;
- }
+ :host([empty]) {
+ display: none;
+ }
- slot {
- display: flex;
- flex-direction: column;
- gap: var(--uui-size-space-4);
- }
+ slot {
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-space-4);
+ }
- #divider {
- width: 100%;
- text-align: center;
- color: var(--uui-color-interactive);
- position: relative;
- z-index: 0;
- margin-bottom: 16px;
- }
+ #divider {
+ width: calc(100% - 18px);
+ margin: 0 auto;
+ margin-bottom: 16px;
+ text-align: center;
+ z-index: 0;
+ overflow: hidden;
+ }
- #divider span {
- background-color: var(--uui-color-surface-alt);
- padding: 0 9px;
- text-transform: capitalize;
- }
+ #divider span {
+ padding-inline: 10px;
+ position: relative;
+ color: var(--uui-color-border-emphasis);
+ }
- #divider::before {
- content: '';
- display: block;
- width: 100%;
- height: 1px;
- background-color: var(--uui-color-border);
- position: absolute;
- top: calc(50% + 1px);
- transform: translateY(-50%);
- z-index: -1;
- }
- `,
- ];
+ #divider span::before,
+ #divider span::after {
+ content: '';
+ display: block;
+ width: 500px; /* Arbitrary value, just be bigger than 50% of the max width of the container */
+ height: 1px;
+ background-color: var(--uui-color-border);
+ position: absolute;
+ top: calc(50% + 1px);
+ }
+
+ #divider span::before {
+ right: 100%;
+ }
+
+ #divider span::after {
+ left: 100%;
+ }
+ `,
+ ];
}
declare global {
- interface HTMLElementTagNameMap {
- 'umb-external-login-providers-layout': UmbExternalLoginProvidersLayoutElement;
- }
+ interface HTMLElementTagNameMap {
+ 'umb-external-login-providers-layout': UmbExternalLoginProvidersLayoutElement;
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/layouts/new-password-layout.element.ts b/src/Umbraco.Web.UI.Login/src/components/layouts/new-password-layout.element.ts
index 4054a78630..3a3a56f0b1 100644
--- a/src/Umbraco.Web.UI.Login/src/components/layouts/new-password-layout.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/layouts/new-password-layout.element.ts
@@ -1,202 +1,234 @@
-import type { UUIButtonState, UUIInputPasswordElement } from '@umbraco-ui/uui';
-import { CSSResultGroup, LitElement, css, html, nothing } from 'lit';
-import { customElement, property, query, state } from 'lit/decorators.js';
-import { until } from 'lit/directives/until.js';
+import type {UUIButtonState, UUIInputPasswordElement} from '@umbraco-ui/uui';
+import {CSSResultGroup, LitElement, css, html, nothing} from 'lit';
+import {customElement, property, query, state} from 'lit/decorators.js';
+import {until} from 'lit/directives/until.js';
-import { umbAuthContext } from '../../context/auth.context.js';
-import { umbLocalizationContext } from '../../external/localization/localization-context.js';
+import {umbAuthContext} from '../../context/auth.context.js';
+import {umbLocalizationContext} from '../../external/localization/localization-context.js';
@customElement('umb-new-password-layout')
export default class UmbNewPasswordLayoutElement extends LitElement {
- @query('#password')
- passwordElement!: UUIInputPasswordElement;
+ @query('#password')
+ passwordElement!: UUIInputPasswordElement;
- @query('#confirmPassword')
- confirmPasswordElement!: UUIInputPasswordElement;
+ @query('#confirmPassword')
+ confirmPasswordElement!: UUIInputPasswordElement;
- @property()
- state: UUIButtonState = undefined;
+ @property()
+ state: UUIButtonState = undefined;
- @property()
- error: string = '';
+ @property()
+ error: string = '';
- @property()
- userId: any;
+ @property()
+ userId: any;
- @property()
- userName?: string;
+ @property()
+ userName?: string;
- @state()
- passwordConfig?: {
- allowManuallyChangingPassword: boolean;
- minNonAlphaNumericChars: number;
- minPasswordLength: number;
- };
+ @state()
+ passwordConfig?: {
+ allowManuallyChangingPassword: boolean;
+ minNonAlphaNumericChars: number;
+ minPasswordLength: number;
+ };
- protected async firstUpdated(_changedProperties: any) {
- super.firstUpdated(_changedProperties);
+ protected async firstUpdated(_changedProperties: any) {
+ super.firstUpdated(_changedProperties);
- if (this.userId) {
- const response = await umbAuthContext.getPasswordConfig(this.userId);
- this.passwordConfig = response.data;
- }
- }
+ if (this.userId) {
+ const response = await umbAuthContext.getPasswordConfig(this.userId);
+ this.passwordConfig = response.data;
+ }
+ }
- async #onSubmit(event: Event) {
- event.preventDefault();
- if (!this.passwordConfig) return;
- const form = event.target as HTMLFormElement;
+ async #onSubmit(event: Event) {
+ event.preventDefault();
+ if (!this.passwordConfig) return;
+ const form = event.target as HTMLFormElement;
- this.passwordElement.setCustomValidity('');
- this.confirmPasswordElement.setCustomValidity('');
+ this.passwordElement.setCustomValidity('');
+ this.confirmPasswordElement.setCustomValidity('');
- if (!form) return;
- if (!form.checkValidity()) return;
+ if (!form) return;
+ if (!form.checkValidity()) return;
- const formData = new FormData(form);
- const password = formData.get('password') as string;
- const passwordConfirm = formData.get('confirmPassword') as string;
+ const formData = new FormData(form);
+ const password = formData.get('password') as string;
+ const passwordConfirm = formData.get('confirmPassword') as string;
- let passwordIsInvalid = false;
+ let passwordIsInvalid = false;
- if (this.passwordConfig.minPasswordLength > 0 && password.length < this.passwordConfig.minPasswordLength) {
- passwordIsInvalid = true;
- }
+ if (this.passwordConfig.minPasswordLength > 0 && password.length < this.passwordConfig.minPasswordLength) {
+ passwordIsInvalid = true;
+ }
- if (this.passwordConfig.minNonAlphaNumericChars > 0) {
- const nonAlphaNumericChars = password.replace(/[a-zA-Z0-9]/g, '').length; //TODO: How should we check for non-alphanumeric chars?
- if (nonAlphaNumericChars < this.passwordConfig?.minNonAlphaNumericChars) {
- passwordIsInvalid = true;
- }
- }
+ if (this.passwordConfig.minNonAlphaNumericChars > 0) {
+ const nonAlphaNumericChars = password.replace(/[a-zA-Z0-9]/g, '').length; //TODO: How should we check for non-alphanumeric chars?
+ if (nonAlphaNumericChars < this.passwordConfig?.minNonAlphaNumericChars) {
+ passwordIsInvalid = true;
+ }
+ }
- if (passwordIsInvalid) {
- const passwordValidityText = await umbLocalizationContext.localize('errorHandling_errorInPasswordFormat', [this.passwordConfig.minPasswordLength, this.passwordConfig.minNonAlphaNumericChars], "The password doesn't meet the minimum requirements!");
- this.passwordElement.setCustomValidity(passwordValidityText);
- return;
- }
+ if (passwordIsInvalid) {
+ const passwordValidityText = await umbLocalizationContext.localize(
+ 'errorHandling_errorInPasswordFormat',
+ [this.passwordConfig.minPasswordLength, this.passwordConfig.minNonAlphaNumericChars],
+ "The password doesn't meet the minimum requirements!"
+ );
+ this.passwordElement.setCustomValidity(passwordValidityText);
+ return;
+ }
- if (password !== passwordConfirm) {
- const passwordValidityText = await umbLocalizationContext.localize('user_passwordMismatch', undefined, "The confirmed password doesn't match the new password!");
- this.confirmPasswordElement.setCustomValidity(passwordValidityText);
- return;
- }
+ if (password !== passwordConfirm) {
+ const passwordValidityText = await umbLocalizationContext.localize(
+ 'user_passwordMismatch',
+ undefined,
+ "The confirmed password doesn't match the new password!"
+ );
+ this.confirmPasswordElement.setCustomValidity(passwordValidityText);
+ return;
+ }
- this.dispatchEvent(new CustomEvent('submit', { detail: { password } }));
- }
+ this.dispatchEvent(new CustomEvent('submit', {detail: {password}}));
+ }
- renderHeader() {
- if (this.userName) {
- return html`
- Hi, ${this.userName}
- Welcome to Umbraco! Just need to get your password setup and then you're good to go
- `;
- } else {
- return html`
- New password
- Please provide a new password.
- `;
- }
- }
+ renderHeader() {
+ if (this.userName) {
+ return html`
+ Hi, ${this.userName}
+
+
+ Welcome to Umbraco! Just need to get your password setup and then you're good to go
+
+
+ `;
+ } else {
+ return html`
+
+ New password
+
+
+ Please provide a new password.
+
+ `;
+ }
+ }
- render() {
- return html`
-
-
+
-
- `;
- }
+
+ `;
+ }
- #renderErrorMessage() {
- if (!this.error || this.state !== 'failed') return nothing;
+ #renderErrorMessage() {
+ if (!this.error || this.state !== 'failed') return nothing;
- return html`${this.error}`;
- }
+ return html`${this.error}`;
+ }
- static styles: CSSResultGroup = [
- css`
- #header {
- text-align: center;
- display: flex;
- flex-direction: column;
- gap: var(--uui-size-space-5);
- }
- #header span {
- color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */
- font-size: 14px;
- }
- #header h2 {
- margin: 0px;
- font-weight: bold;
- font-size: 1.4rem;
- }
- form {
- display: flex;
- flex-direction: column;
- gap: var(--uui-size-space-5);
- }
- uui-form-layout-item {
- margin: 0;
- }
- h2 {
- margin: 0px;
- font-weight: 600;
- font-size: 1.4rem;
- margin-bottom: var(--uui-size-space-4);
- }
- uui-input-password {
- width: 100%;
- }
- uui-button {
- width: 100%;
- margin-top: var(--uui-size-space-5);
- --uui-button-padding-top-factor: 1.5;
- --uui-button-padding-bottom-factor: 1.5;
- }
- .text-danger {
- color: var(--uui-color-danger-standalone);
- }
- `,
- ];
+ static styles: CSSResultGroup = [
+ css`
+ #header {
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-space-5);
+ }
+
+ #header span {
+ color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */
+ font-size: 14px;
+ }
+
+ #header h1 {
+ margin: 0;
+ font-weight: 400;
+ font-size: var(--header-secondary-font-size);
+ color: var(--uui-color-interactive);
+ line-height: 1.2;
+ }
+
+ form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-space-5);
+ }
+
+ uui-form-layout-item {
+ margin: 0;
+ }
+
+ uui-input-password {
+ width: 100%;
+ height: var(--input-height);
+ border-radius: var(--uui-border-radius);
+ }
+
+ uui-button {
+ width: 100%;
+ margin-top: var(--uui-size-space-5);
+ --uui-button-padding-top-factor: 1.5;
+ --uui-button-padding-bottom-factor: 1.5;
+ }
+
+ .text-danger {
+ color: var(--uui-color-danger-standalone);
+ }
+ `,
+ ];
}
declare global {
- interface HTMLElementTagNameMap {
- 'umb-new-password-layout': UmbNewPasswordLayoutElement;
- }
+ interface HTMLElementTagNameMap {
+ 'umb-new-password-layout': UmbNewPasswordLayoutElement;
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/login-input.element.ts b/src/Umbraco.Web.UI.Login/src/components/login-input.element.ts
index 2ac20f8267..d3e6262172 100644
--- a/src/Umbraco.Web.UI.Login/src/components/login-input.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/login-input.element.ts
@@ -10,7 +10,6 @@ import { customElement } from 'lit/decorators.js';
*/
@customElement('umb-login-input')
export class UmbLoginInputElement extends UUIInputElement {
-
/**
* Remove the id attribute from the inner input element to avoid duplicate ids.
*
@@ -18,8 +17,15 @@ export class UmbLoginInputElement extends UUIInputElement {
* @protected
*/
protected firstUpdated() {
- const innerInput = this.querySelector('input')
+ const innerInput = this.querySelector('input');
innerInput?.removeAttribute('id');
+
+ innerInput?.addEventListener('mousedown', () => {
+ this.style.setProperty('--uui-show-focus-outline', '0');
+ });
+ innerInput?.addEventListener('blur', () => {
+ this.style.setProperty('--uui-show-focus-outline', '');
+ });
}
/**
diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/invite.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/invite.page.element.ts
index a5bd23aae7..9c5a8b215a 100644
--- a/src/Umbraco.Web.UI.Login/src/components/pages/invite.page.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/pages/invite.page.element.ts
@@ -1,5 +1,5 @@
import type { UUIButtonState } from '@umbraco-ui/uui';
-import { LitElement, html, nothing } from 'lit';
+import { LitElement, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { until } from 'lit/directives/until.js';
@@ -8,64 +8,74 @@ import { umbLocalizationContext } from '../../external/localization/localization
@customElement('umb-invite-page')
export default class UmbInvitePageElement extends LitElement {
- @state()
- state: UUIButtonState = undefined;
+ @state()
+ state: UUIButtonState = undefined;
- @state()
- error = '';
+ @state()
+ error = '';
- @state()
- invitedUser?: any;
+ @state()
+ invitedUser?: any;
- protected async firstUpdated(_changedProperties: any) {
- super.firstUpdated(_changedProperties);
+ protected async firstUpdated(_changedProperties: any) {
+ super.firstUpdated(_changedProperties);
- const response = await umbAuthContext.getInvitedUser();
+ const response = await umbAuthContext.getInvitedUser();
- if (!response.user?.id) {
- // The login page should already have redirected the user to an error page. They should never get here.
- this.error = 'No invited user found';
- return;
- }
+ if (!response.user?.id) {
+ // The login page should already have redirected the user to an error page. They should never get here.
+ this.error = 'No invited user found';
+ return;
+ }
- this.invitedUser = response.user;
- }
+ this.invitedUser = response.user;
+ }
- async #onSubmit(event: CustomEvent) {
- event.preventDefault();
- const password = event.detail.password;
+ async #onSubmit(event: CustomEvent) {
+ event.preventDefault();
+ const password = event.detail.password;
- if (!password) return;
+ if (!password) return;
- this.state = 'waiting';
- const response = await umbAuthContext.newInvitedUserPassword(password);
+ this.state = 'waiting';
+ const response = await umbAuthContext.newInvitedUserPassword(password);
- if (response.error) {
- this.error = response.error;
- this.state = 'failed';
- return;
- }
+ if (response.error) {
+ this.error = response.error;
+ this.state = 'failed';
+ return;
+ }
- this.state = 'success';
- window.location.href = umbAuthContext.returnPath;
- }
+ this.state = 'success';
+ window.location.href = umbAuthContext.returnPath;
+ }
- render() {
- return this.invitedUser
- ? html``
- : this.error
- ? html``
- : nothing;
- }
+ render() {
+ return this.invitedUser
+ ? html`
+ `
+ : this.error
+ ? html`
+ `
+ : html`
+
+ `;
+ }
}
declare global {
- interface HTMLElementTagNameMap {
- 'umb-invite-page': UmbInvitePageElement;
- }
+ interface HTMLElementTagNameMap {
+ 'umb-invite-page': UmbInvitePageElement;
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts
index ca42466c6b..460b867896 100644
--- a/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/pages/login.page.element.ts
@@ -1,21 +1,21 @@
-import type { UUIButtonState } from '@umbraco-ui/uui';
-import { css, CSSResultGroup, html, LitElement, nothing } from 'lit';
-import { customElement, property, queryAssignedElements, state } from 'lit/decorators.js';
-import { when } from 'lit/directives/when.js';
-import { until } from 'lit/directives/until.js';
+import type {UUIButtonState} from '@umbraco-ui/uui';
+import {css, CSSResultGroup, html, LitElement, nothing} from 'lit';
+import {customElement, property, queryAssignedElements, state} from 'lit/decorators.js';
+import {when} from 'lit/directives/when.js';
+import {until} from 'lit/directives/until.js';
-import { umbAuthContext } from '../../context/auth.context.js';
-import { umbLocalizationContext } from '../../external/localization/localization-context.js';
+import {umbAuthContext} from '../../context/auth.context.js';
+import {umbLocalizationContext} from '../../external/localization/localization-context.js';
@customElement('umb-login-page')
export default class UmbLoginPageElement extends LitElement {
- @property({ type: Boolean, attribute: 'username-is-email' })
+ @property({type: Boolean, attribute: 'username-is-email'})
usernameIsEmail = false;
- @queryAssignedElements({ flatten: true })
+ @queryAssignedElements({flatten: true})
protected slottedElements?: HTMLFormElement[];
- @property({ type: Boolean, attribute: 'allow-password-reset' })
+ @property({type: Boolean, attribute: 'allow-password-reset'})
allowPasswordReset = false;
@state()
@@ -32,7 +32,7 @@ export default class UmbLoginPageElement extends LitElement {
#formElement?: HTMLFormElement;
async #onSlotChanged() {
- this.#formElement = this.slottedElements?.[0];
+ this.#formElement = this.slottedElements?.find((el) => el.id === 'umb-login-form');
if (!this.#formElement) return;
@@ -71,12 +71,12 @@ export default class UmbLoginPageElement extends LitElement {
umbAuthContext.twoFactorView = response.twoFactorView;
}
- this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: 'mfa' } }));
+ this.dispatchEvent(new CustomEvent('umb-login-flow', {composed: true, detail: {flow: 'mfa'}}));
return;
}
if (response.error) {
- this.dispatchEvent(new CustomEvent('umb-login-failed', { bubbles: true, composed: true, detail: response }));
+ this.dispatchEvent(new CustomEvent('umb-login-failed', {bubbles: true, composed: true, detail: response}));
return;
}
@@ -86,69 +86,59 @@ export default class UmbLoginPageElement extends LitElement {
location.href = returnPath;
}
- this.dispatchEvent(new CustomEvent('umb-login-success', { bubbles: true, composed: true, detail: response.data }));
+ this.dispatchEvent(new CustomEvent('umb-login-success', {bubbles: true, composed: true, detail: response.data}));
};
- get #greetingLocalizationKey() {
- return [
- 'login_greeting0',
- 'login_greeting1',
- 'login_greeting2',
- 'login_greeting3',
- 'login_greeting4',
- 'login_greeting5',
- 'login_greeting6',
- ][new Date().getDay()];
- }
-
#onSubmitClick = () => {
this.#formElement?.requestSubmit();
};
render() {
return html`
-
-
-
-
- ${this.disableLocalLogin
- ? nothing
- : html`
-
-
- ${when(
- umbAuthContext.supportsPersistLogin,
- () => html`
-
- Remember me
-
- `
- )}
- ${when(
- this.allowPasswordReset,
- () =>
- html``
- )}
-
-
+
+ Welcome
+
+
+ ${this.disableLocalLogin
+ ? nothing
+ : html`
+
+
+ ${when(
+ umbAuthContext.supportsPersistLogin,
+ () => html`
+
+
+ Remember me
+
+ `
+ )}
+ ${when(
+ this.allowPasswordReset,
+ () =>
+ html`
+ `
+ )}
+
+
- ${this.#renderErrorMessage()}
- `}
-
-
-
- `;
+ ${this.#renderErrorMessage()}
+ `}
+
+
+
+ `;
}
#renderErrorMessage() {
@@ -158,7 +148,7 @@ export default class UmbLoginPageElement extends LitElement {
}
#handleForgottenPassword() {
- this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: 'reset' } }));
+ this.dispatchEvent(new CustomEvent('umb-login-flow', {composed: true, detail: {flow: 'reset'}}));
}
static styles: CSSResultGroup = [
@@ -172,7 +162,7 @@ export default class UmbLoginPageElement extends LitElement {
color: var(--uui-color-interactive);
text-align: center;
font-weight: 400;
- font-size: 1.5rem;
+ font-size: var(--header-font-size);
margin: 0 0 var(--uui-size-layout-1);
line-height: 1.2;
}
@@ -180,8 +170,6 @@ export default class UmbLoginPageElement extends LitElement {
#umb-login-button {
margin-top: var(--uui-size-space-4);
width: 100%;
- --uui-button-padding-top-factor: 1.5;
- --uui-button-padding-bottom-factor: 1.5;
}
#forgot-password {
@@ -197,12 +185,18 @@ export default class UmbLoginPageElement extends LitElement {
line-height: 1;
font-size: 14px;
font-family: var(--uui-font-family);
+ margin-left: auto;
+ margin-bottom: var(--uui-size-space-3);
}
#forgot-password:hover {
color: var(--uui-color-interactive-emphasis);
}
+ .text-error {
+ margin-top: var(--uui-size-space-4);
+ }
+
.text-danger {
color: var(--uui-color-danger-standalone);
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/mfa.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/mfa.page.element.ts
index 8369c333a1..435dd8684e 100644
--- a/src/Umbraco.Web.UI.Login/src/components/pages/mfa.page.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/pages/mfa.page.element.ts
@@ -1,243 +1,285 @@
-import type { UUIButtonState, UUIInputElement } from '@umbraco-ui/uui';
-import { LitElement, css, html, nothing } from 'lit';
-import { customElement, state } from 'lit/decorators.js';
-import { until } from 'lit/directives/until.js';
-import { umbAuthContext } from '../../context/auth.context.js';
-import { umbLocalizationContext } from '../../external/localization/localization-context.js';
-import { loadCustomView, renderCustomView } from '../../utils/load-custom-view.function.js';
+import type {UUIButtonState, UUIInputElement} from '@umbraco-ui/uui';
+import {LitElement, css, html, nothing} from 'lit';
+import {customElement, state} from 'lit/decorators.js';
+import {until} from 'lit/directives/until.js';
+import {umbAuthContext} from '../../context/auth.context.js';
+import {umbLocalizationContext} from '../../external/localization/localization-context.js';
+import {loadCustomView, renderCustomView} from '../../utils/load-custom-view.function.js';
type MfaCustomViewElement = HTMLElement & {
- providers?: string[];
+ providers?: string[];
returnPath?: string;
};
@customElement('umb-mfa-page')
export default class UmbMfaPageElement extends LitElement {
- @state()
- protected providers: Array<{ name: string; value: string; selected: boolean }> = [];
+ @state()
+ protected providers: Array<{ name: string; value: string; selected: boolean }> = [];
- @state()
- private loading = true;
+ @state()
+ private loading = true;
- @state()
- private buttonState?: UUIButtonState;
+ @state()
+ private buttonState?: UUIButtonState;
- @state()
- private error: string | null = null;
+ @state()
+ private error: string | null = null;
- constructor() {
- super();
- this.#loadProviders();
- }
+ constructor() {
+ super();
+ this.#loadProviders();
+ }
- async #loadProviders() {
- try {
- const response = await umbAuthContext.getMfaProviders();
- this.providers = response.providers.map((provider) => ({ name: provider, value: provider, selected: false }));
+ async #loadProviders() {
+ try {
+ const response = await umbAuthContext.getMfaProviders();
+ this.providers = response.providers.map((provider) => ({name: provider, value: provider, selected: false}));
- if (this.providers.length) {
- this.providers[0].selected = true;
- }
+ if (this.providers.length) {
+ this.providers[0].selected = true;
+ }
- if (response.error) {
- this.error = response.error;
- }
- } catch (e) {
- if (e instanceof Error) {
- this.error = e.message ?? 'Unknown error';
- } else {
- this.error = 'Unknown error';
- }
- this.providers = [];
- }
- this.loading = false;
- }
+ if (response.error) {
+ this.error = response.error;
+ }
+ } catch (e) {
+ if (e instanceof Error) {
+ this.error = e.message ?? 'Unknown error';
+ } else {
+ this.error = 'Unknown error';
+ }
+ this.providers = [];
+ }
+ this.loading = false;
+ }
- private async handleSubmit(e: SubmitEvent) {
- e.preventDefault();
+ private async handleSubmit(e: SubmitEvent) {
+ e.preventDefault();
- this.error = null;
+ this.error = null;
- const form = e.target as HTMLFormElement;
- if (!form) return;
+ const form = e.target as HTMLFormElement;
+ if (!form) return;
- const codeInput = form.elements.namedItem('2facode') as UUIInputElement;
+ const codeInput = form.elements.namedItem('2facode') as UUIInputElement;
- if (codeInput) {
- codeInput.error = false;
- codeInput.errorMessage = '';
- }
+ if (codeInput) {
+ codeInput.error = false;
+ codeInput.errorMessage = '';
+ }
- if (!form.checkValidity()) return;
+ if (!form.checkValidity()) return;
- const formData = new FormData(form);
+ const formData = new FormData(form);
- let provider = formData.get('provider') as string;
+ let provider = formData.get('provider') as string;
- // If no provider given, use the first one (there probably is only one anyway)
- if (!provider) {
- provider = this.providers[0].value;
- }
+ // If no provider given, use the first one (there probably is only one anyway)
+ if (!provider) {
+ provider = this.providers[0].value;
+ }
- if (!provider) {
- this.error = 'No provider selected';
- return;
- }
+ if (!provider) {
+ this.error = 'No provider selected';
+ return;
+ }
- const code = formData.get('token') as string;
+ const code = formData.get('token') as string;
- this.buttonState = 'waiting';
+ this.buttonState = 'waiting';
- try {
- const response = await umbAuthContext.validateMfaCode(code, provider);
- if (response.error) {
- if (codeInput) {
- codeInput.error = true;
- codeInput.errorMessage = response.error;
- } else {
- this.error = response.error;
- }
- this.buttonState = 'failed';
- return;
- }
+ try {
+ const response = await umbAuthContext.validateMfaCode(code, provider);
+ if (response.error) {
+ if (codeInput) {
+ codeInput.error = true;
+ codeInput.errorMessage = response.error;
+ } else {
+ this.error = response.error;
+ }
+ this.buttonState = 'failed';
+ return;
+ }
- this.buttonState = 'success';
+ this.buttonState = 'success';
- const returnPath = umbAuthContext.returnPath;
- if (returnPath) {
- location.href = returnPath;
- }
+ const returnPath = umbAuthContext.returnPath;
+ if (returnPath) {
+ location.href = returnPath;
+ }
- this.dispatchEvent(new CustomEvent('umb-login-success', { bubbles: true, composed: true, detail: response.data }));
- } catch (e) {
- if (e instanceof Error) {
- this.error = e.message ?? 'Unknown error';
- } else {
- this.error = 'Unknown error';
- }
- this.buttonState = 'failed';
- this.dispatchEvent(new CustomEvent('umb-login-failed', { bubbles: true, composed: true, detail: e }));
- }
- }
+ this.dispatchEvent(
+ new CustomEvent('umb-login-success', {bubbles: true, composed: true, detail: response.data})
+ );
+ } catch (e) {
+ if (e instanceof Error) {
+ this.error = e.message ?? 'Unknown error';
+ } else {
+ this.error = 'Unknown error';
+ }
+ this.buttonState = 'failed';
+ this.dispatchEvent(new CustomEvent('umb-login-failed', {bubbles: true, composed: true, detail: e}));
+ }
+ }
- protected renderDefaultView() {
- return html`
-
-
-
+ protected renderDefaultView() {
+ return html`
+
+
+
-
- ${this.providers.length > 1
- ? html`
-
-
- Please choose a 2-factor provider
-
-
-
-
- `
- : nothing}
+
+ ${this.providers.length > 1
+ ? html`
+
+
+ Please choose a 2-factor provider
+
+
+
+
+
+
+ `
+ : nothing}
-
-
- Verification code
-
+
+
+ Verification code
+
-
-
-
+
+
+
-
-
-
-
- ${this.error ? html` ${this.error} ` : nothing}
-
-
- `;
- }
+ ${this.error ? html` ${this.error} ` : nothing}
- protected async renderCustomView() {
- const view = umbAuthContext.twoFactorView;
- if (!view) return nothing;
+
+
+
- try {
- const customView = await loadCustomView(view);
- if (typeof customView === 'object') {
- customView.providers = this.providers.map((provider) => provider.value);
+
+ `;
+ }
+
+ protected async renderCustomView() {
+ const view = umbAuthContext.twoFactorView;
+ if (!view) return nothing;
+
+ try {
+ const customView = await loadCustomView(view);
+ if (typeof customView === 'object') {
+ customView.providers = this.providers.map((provider) => provider.value);
customView.returnPath = umbAuthContext.returnPath;
- }
- return renderCustomView(customView);
- } catch (e) {
- const error = e instanceof Error ? e.message : 'Unknown error';
- console.group('[MFA login] Failed to load custom view');
- console.log('Element reference', this);
- console.log('Custom view', view);
- console.error('Failed to load custom view:', e);
- console.groupEnd();
- return html`${error}`;
- }
- }
+ }
+ return renderCustomView(customView);
+ } catch (e) {
+ const error = e instanceof Error ? e.message : 'Unknown error';
+ console.group('[MFA login] Failed to load custom view');
+ console.log('Element reference', this);
+ console.log('Custom view', view);
+ console.error('Failed to load custom view:', e);
+ console.groupEnd();
+ return html`${error}`;
+ }
+ }
- protected render() {
- return this.loading
- ? html``
- : umbAuthContext.twoFactorView
- ? until(this.renderCustomView(), html``)
- : this.renderDefaultView();
- }
+ protected render() {
+ return this.loading
+ ? html`
+ `
+ : umbAuthContext.twoFactorView
+ ? until(this.renderCustomView(), html`
+ `)
+ : this.renderDefaultView();
+ }
- private onGoBack() {
- this.dispatchEvent(new CustomEvent('umb-login-flow', { composed: true, detail: { flow: undefined } }));
- }
+ static styles = [
+ css`
+ #header {
+ text-align: center;
+ }
- static styles = [
- css`
- .text-danger {
- color: var(--uui-color-danger-standalone);
- }
- `,
- ];
+ #header h1 {
+ font-weight: 400;
+ font-size: var(--header-secondary-font-size);
+ color: var(--uui-color-interactive);
+ line-height: 1.2;
+ }
+
+ form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-layout-2);
+ }
+
+ .uui-input-wrapper {
+ background-color: var(--uui-color-surface);
+ }
+
+ uui-form-layout-item {
+ margin: 0;
+ }
+
+ uui-input,
+ uui-input-password {
+ width: 100%;
+ height: var(--input-height);
+ border-radius: var(--uui-border-radius);
+ }
+
+ uui-input {
+ width: 100%;
+ }
+
+ uui-button {
+ width: 100%;
+ --uui-button-padding-top-factor: 1.5;
+ --uui-button-padding-bottom-factor: 1.5;
+ }
+
+ .text-danger {
+ color: var(--uui-color-danger-standalone);
+ }
+ `,
+ ];
}
declare global {
- interface HTMLElementTagNameMap {
- 'umb-mfa-page': UmbMfaPageElement;
- }
+ interface HTMLElementTagNameMap {
+ 'umb-mfa-page': UmbMfaPageElement;
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/new-password.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/new-password.page.element.ts
index bc8f44b1ac..51a4c81837 100644
--- a/src/Umbraco.Web.UI.Login/src/components/pages/new-password.page.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/pages/new-password.page.element.ts
@@ -1,87 +1,106 @@
-import type { UUIButtonState, UUIInputPasswordElement } from '@umbraco-ui/uui';
-import { LitElement, html, nothing } from 'lit';
-import { customElement, query, state } from 'lit/decorators.js';
-import { until } from 'lit/directives/until.js';
+import type {UUIButtonState, UUIInputPasswordElement} from '@umbraco-ui/uui';
+import {LitElement, html} from 'lit';
+import {customElement, query, state} from 'lit/decorators.js';
+import {until} from 'lit/directives/until.js';
-import { umbAuthContext } from '../../context/auth.context.js';
-import { umbLocalizationContext } from '../../external/localization/localization-context.js';
+import {umbAuthContext} from '../../context/auth.context.js';
+import {umbLocalizationContext} from '../../external/localization/localization-context.js';
@customElement('umb-new-password-page')
export default class UmbNewPasswordPageElement extends LitElement {
- @query('#confirmPassword')
- confirmPasswordElement!: UUIInputPasswordElement;
+ @query('#confirmPassword')
+ confirmPasswordElement!: UUIInputPasswordElement;
- @state()
- state: UUIButtonState = undefined;
+ @state()
+ state: UUIButtonState = undefined;
- @state()
- page: 'new' | 'done' | 'error' = 'new';
+ @state()
+ page: 'new' | 'done' | 'error' = 'new';
- @state()
- error = '';
+ @state()
+ error = '';
- @state()
- userId: string | null = null;
+ @state()
+ userId: string | null = null;
- @state()
- resetCode: string | null = null;
+ @state()
+ resetCode: string | null = null;
- constructor() {
- super();
+ constructor() {
+ super();
- const urlParams = new URLSearchParams(window.location.search);
- this.resetCode = urlParams.get('resetCode');
- this.userId = urlParams.get('userId');
+ const urlParams = new URLSearchParams(window.location.search);
+ this.resetCode = urlParams.get('resetCode');
+ this.userId = urlParams.get('userId');
- if (!this.userId || !this.resetCode) {
- this.page = 'error';
- }
- }
+ if (!this.userId || !this.resetCode) {
+ this.page = 'error';
+ }
+ }
- async #onSubmit(event: CustomEvent) {
- event.preventDefault();
- const urlParams = new URLSearchParams(window.location.search);
- const resetCode = urlParams.get('resetCode');
- const userId = urlParams.get('userId');
- const password = event.detail.password;
+ async #onSubmit(event: CustomEvent) {
+ event.preventDefault();
+ const urlParams = new URLSearchParams(window.location.search);
+ const resetCode = urlParams.get('resetCode');
+ const userId = urlParams.get('userId');
+ const password = event.detail.password;
- if (!resetCode || !userId) return;
+ if (!resetCode || !userId) return;
- this.state = 'waiting';
- const response = await umbAuthContext.newPassword(password, resetCode, userId);
- this.state = response.status === 200 ? 'success' : 'failed';
- this.page = response.status === 200 ? 'done' : 'new';
- this.error = response.error || '';
- }
+ this.state = 'waiting';
+ const response = await umbAuthContext.newPassword(password, resetCode, userId);
+ this.state = response.status === 200 ? 'success' : 'failed';
+ this.page = response.status === 200 ? 'done' : 'new';
+ this.error = response.error || '';
+ }
- #renderRoutes() {
- switch (this.page) {
- case 'new':
- return html``;
- case 'error':
- return html`
- `;
- case 'done':
- return html`
- `;
- }
- }
+ #renderRoutes() {
+ switch (this.page) {
+ case 'new':
+ return html`
+ `;
+ case 'error':
+ return html`
+
+ `;
+ case 'done':
+ return html`
+
+ `;
+ }
+ }
- render() {
- return this.userId && this.resetCode ? this.#renderRoutes() : nothing;
- }
+ render() {
+ return this.userId && this.resetCode
+ ? this.#renderRoutes()
+ : html`
+
+ `;
+ }
}
declare global {
- interface HTMLElementTagNameMap {
- 'umb-new-password-page': UmbNewPasswordPageElement;
- }
+ interface HTMLElementTagNameMap {
+ 'umb-new-password-page': UmbNewPasswordPageElement;
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/components/pages/reset-password.page.element.ts b/src/Umbraco.Web.UI.Login/src/components/pages/reset-password.page.element.ts
index dc77010105..d47fbce504 100644
--- a/src/Umbraco.Web.UI.Login/src/components/pages/reset-password.page.element.ts
+++ b/src/Umbraco.Web.UI.Login/src/components/pages/reset-password.page.element.ts
@@ -1,155 +1,176 @@
-import type { UUIButtonState } from '@umbraco-ui/uui';
-import { CSSResultGroup, LitElement, css, html, nothing } from 'lit';
-import { customElement, state } from 'lit/decorators.js';
-import { until } from 'lit/directives/until.js';
+import type {UUIButtonState} from '@umbraco-ui/uui';
+import {CSSResultGroup, LitElement, css, html, nothing} from 'lit';
+import {customElement, state} from 'lit/decorators.js';
+import {until} from 'lit/directives/until.js';
-import { umbAuthContext } from '../../context/auth.context.js';
-import { umbLocalizationContext } from '../../external/localization/localization-context.js';
+import {umbAuthContext} from '../../context/auth.context.js';
+import {umbLocalizationContext} from '../../external/localization/localization-context.js';
@customElement('umb-reset-password-page')
export default class UmbResetPasswordPageElement extends LitElement {
- @state()
- resetCallState: UUIButtonState = undefined;
+ @state()
+ resetCallState: UUIButtonState = undefined;
- @state()
- error = '';
+ @state()
+ error = '';
- #handleResetSubmit = async (e: SubmitEvent) => {
- e.preventDefault();
- const form = e.target as HTMLFormElement;
+ #handleResetSubmit = async (e: SubmitEvent) => {
+ e.preventDefault();
+ const form = e.target as HTMLFormElement;
- if (!form) return;
- if (!form.checkValidity()) return;
+ if (!form) return;
+ if (!form.checkValidity()) return;
- const formData = new FormData(form);
- const username = formData.get('email') as string;
+ const formData = new FormData(form);
+ const username = formData.get('email') as string;
- this.resetCallState = 'waiting';
- const response = await umbAuthContext.resetPassword(username);
- this.resetCallState = response.status === 200 ? 'success' : 'failed';
- this.error = response.error || '';
- };
+ this.resetCallState = 'waiting';
+ const response = await umbAuthContext.resetPassword(username);
+ this.resetCallState = response.status === 200 ? 'success' : 'failed';
+ this.error = response.error || '';
+ };
- #renderResetPage() {
- return html`
-
-
-
+
-
-
- Email
-
-
-
+
+
+ Email
+
+
+
- ${this.#renderErrorMessage()}
+ ${this.#renderErrorMessage()}
-
-
-
+
+
+
-
- `;
- }
+
+ `;
+ }
- #renderErrorMessage() {
- if (!this.error || this.resetCallState !== 'failed') return nothing;
+ #renderErrorMessage() {
+ if (!this.error || this.resetCallState !== 'failed') return nothing;
- return html`${this.error}`;
- }
+ return html`${this.error}`;
+ }
- #renderConfirmationPage() {
- return html`
-
-
- `;
- }
+ #renderConfirmationPage() {
+ return html`
+
+
+ `;
+ }
- render() {
- return this.resetCallState === 'success' ? this.#renderConfirmationPage() : this.#renderResetPage();
- }
+ render() {
+ return this.resetCallState === 'success' ? this.#renderConfirmationPage() : this.#renderResetPage();
+ }
- static styles: CSSResultGroup = [
- css`
- #header {
- text-align: center;
- display: flex;
- flex-direction: column;
- gap: var(--uui-size-space-5);
- }
- #header span {
- color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */
- font-size: 14px;
- }
- #header h2 {
- margin: 0px;
- font-weight: bold;
- font-size: 1.4rem;
- }
- form {
- display: flex;
- flex-direction: column;
- gap: var(--uui-size-layout-2);
- }
- uui-form-layout-item {
- margin: 0;
- }
- uui-input,
- uui-input-password {
- width: 100%;
- border-radius: var(--uui-border-radius);
- }
- uui-input {
- width: 100%;
- }
- uui-button {
- width: 100%;
- --uui-button-padding-top-factor: 1.5;
- --uui-button-padding-bottom-factor: 1.5;
- }
+ static styles: CSSResultGroup = [
+ css`
+ #header {
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-space-5);
+ }
- #resend {
- display: inline-flex;
- font-size: 14px;
- align-self: center;
- gap: var(--uui-size-space-1);
- }
+ #header span {
+ color: var(--uui-color-text-alt); /* TODO Change to uui color when uui gets a muted text variable */
+ font-size: 14px;
+ }
- #resend a {
- color: var(--uui-color-selected);
- font-weight: 600;
- text-decoration: none;
- }
- #resend a:hover {
- color: var(--uui-color-interactive-emphasis);
- }
- `,
- ];
+ #header h1 {
+ margin: 0;
+ font-weight: 400;
+ font-size: var(--header-secondary-font-size);
+ color: var(--uui-color-interactive);
+ line-height: 1.2;
+ }
+
+ form {
+ display: flex;
+ flex-direction: column;
+ gap: var(--uui-size-layout-2);
+ }
+
+ uui-form-layout-item {
+ margin: 0;
+ }
+
+ uui-input,
+ uui-input-password {
+ width: 100%;
+ height: var(--input-height);
+ border-radius: var(--uui-border-radius);
+ }
+
+ uui-input {
+ width: 100%;
+ }
+
+ uui-button {
+ width: 100%;
+ --uui-button-padding-top-factor: 1.5;
+ --uui-button-padding-bottom-factor: 1.5;
+ }
+
+ #resend {
+ display: inline-flex;
+ font-size: 14px;
+ align-self: center;
+ gap: var(--uui-size-space-1);
+ }
+
+ #resend a {
+ color: var(--uui-color-selected);
+ font-weight: 600;
+ text-decoration: none;
+ }
+
+ #resend a:hover {
+ color: var(--uui-color-interactive-emphasis);
+ }
+ `,
+ ];
}
declare global {
- interface HTMLElementTagNameMap {
- 'umb-reset-password-page': UmbResetPasswordPageElement;
- }
+ interface HTMLElementTagNameMap {
+ 'umb-reset-password-page': UmbResetPasswordPageElement;
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/context/auth.context.ts b/src/Umbraco.Web.UI.Login/src/context/auth.context.ts
index b84ecfd039..b27179d099 100644
--- a/src/Umbraco.Web.UI.Login/src/context/auth.context.ts
+++ b/src/Umbraco.Web.UI.Login/src/context/auth.context.ts
@@ -1,21 +1,21 @@
import {
- LoginRequestModel,
- IUmbAuthContext,
- LoginResponse,
- ResetPasswordResponse,
- ValidatePasswordResetCodeResponse,
- NewPasswordResponse,
- MfaProvidersResponse,
+ LoginRequestModel,
+ IUmbAuthContext,
+ LoginResponse,
+ ResetPasswordResponse,
+ ValidatePasswordResetCodeResponse,
+ NewPasswordResponse,
+ MfaProvidersResponse,
} from '../types.js';
-import { UmbAuthRepository } from './auth.repository.js';
+import {UmbAuthRepository} from './auth.repository.js';
export class UmbAuthContext implements IUmbAuthContext {
- readonly supportsPersistLogin = false;
- disableLocalLogin = false;
+ readonly supportsPersistLogin = false;
+ disableLocalLogin = false;
- #authRepository = new UmbAuthRepository();
+ #authRepository = new UmbAuthRepository();
- #returnPath = '';
+ #returnPath = '';
set returnPath(value: string) {
this.#returnPath = value;
@@ -30,52 +30,65 @@ export class UmbAuthContext implements IUmbAuthContext {
*/
get returnPath(): string {
const params = new URLSearchParams(window.location.search);
- let returnUrl = params.get('ReturnUrl') ?? params.get('returnPath') ?? this.#returnPath;
+ let returnPath = params.get('ReturnUrl') ?? params.get('returnPath') ?? this.#returnPath;
// Paths from the old Backoffice are encoded twice and need to be decoded,
// but we don't want to decode the new paths coming from the Management API.
- if (returnUrl.indexOf('/security/back-office/authorize') === -1) {
- returnUrl = decodeURIComponent(returnUrl);
+ if (returnPath.indexOf('/security/back-office/authorize') === -1) {
+ returnPath = decodeURIComponent(returnPath);
}
- return returnUrl || '';
+
+ // If return path is empty, return an empty string.
+ if (!returnPath) {
+ return '';
+ }
+
+ // Safely check that the return path is valid and doesn't link to an external site.
+ const url = new URL(returnPath, window.location.origin);
+
+ if (url.origin !== window.location.origin) {
+ return '';
+ }
+
+ return url.toString();
}
- async login(data: LoginRequestModel): Promise {
- return this.#authRepository.login(data);
- }
+ async login(data: LoginRequestModel): Promise {
+ return this.#authRepository.login(data);
+ }
- async resetPassword(username: string): Promise {
- return this.#authRepository.resetPassword(username);
- }
+ async resetPassword(username: string): Promise {
+ return this.#authRepository.resetPassword(username);
+ }
- async validatePasswordResetCode(userId: string, resetCode: string): Promise {
- return this.#authRepository.validatePasswordResetCode(userId, resetCode);
- }
+ async validatePasswordResetCode(userId: string, resetCode: string): Promise {
+ return this.#authRepository.validatePasswordResetCode(userId, resetCode);
+ }
- async newPassword(password: string, resetCode: string, userId: string): Promise {
- const userIdAsNumber = Number.parseInt(userId);
- return this.#authRepository.newPassword(password, resetCode, userIdAsNumber);
- }
+ async newPassword(password: string, resetCode: string, userId: string): Promise {
+ const userIdAsNumber = Number.parseInt(userId);
+ return this.#authRepository.newPassword(password, resetCode, userIdAsNumber);
+ }
- async newInvitedUserPassword(password: string): Promise {
- return this.#authRepository.newInvitedUserPassword(password);
- }
+ async newInvitedUserPassword(password: string): Promise {
+ return this.#authRepository.newInvitedUserPassword(password);
+ }
- async getPasswordConfig(userId: string): Promise {
- return this.#authRepository.getPasswordConfig(userId);
- }
+ async getPasswordConfig(userId: string): Promise {
+ return this.#authRepository.getPasswordConfig(userId);
+ }
- async getInvitedUser(): Promise {
- return this.#authRepository.getInvitedUser();
- }
+ async getInvitedUser(): Promise {
+ return this.#authRepository.getInvitedUser();
+ }
- getMfaProviders(): Promise {
- return this.#authRepository.getMfaProviders();
- }
+ getMfaProviders(): Promise {
+ return this.#authRepository.getMfaProviders();
+ }
- validateMfaCode(code: string, provider: string): Promise {
- return this.#authRepository.validateMfaCode(code, provider);
- }
+ validateMfaCode(code: string, provider: string): Promise {
+ return this.#authRepository.validateMfaCode(code, provider);
+ }
}
export const umbAuthContext = new UmbAuthContext() as IUmbAuthContext;
diff --git a/src/Umbraco.Web.UI.Login/src/context/auth.repository.ts b/src/Umbraco.Web.UI.Login/src/context/auth.repository.ts
index 24c1cfe015..69743bcee7 100644
--- a/src/Umbraco.Web.UI.Login/src/context/auth.repository.ts
+++ b/src/Umbraco.Web.UI.Login/src/context/auth.repository.ts
@@ -8,7 +8,7 @@ import type {
import { umbLocalizationContext } from '../external/localization/localization-context.js';
export class UmbAuthRepository {
- readonly #authURL = 'backoffice/umbracoapi/authentication/postlogin';
+ readonly #authURL = 'backoffice/umbracoapi/authentication/postlogin';
public async login(data: LoginRequestModel): Promise {
try {
@@ -26,7 +26,7 @@ export class UmbAuthRepository {
const response = await fetch(request);
const responseData: LoginResponse = {
- status: response.status
+ status: response.status,
};
if (!response.ok) {
@@ -39,7 +39,8 @@ export class UmbAuthRepository {
if (text) {
responseData.data = JSON.parse(this.#removeAngularJSResponseData(text));
}
- } catch {}
+ } catch {
+ }
return {
status: response.status,
@@ -249,16 +250,35 @@ export class UmbAuthRepository {
switch (response.status) {
case 400:
case 401:
- return umbLocalizationContext.localize('login_userFailedLogin', undefined, "Oops! We couldn't log you in. Please check your credentials and try again.");
+ return umbLocalizationContext.localize(
+ 'login_userFailedLogin',
+ undefined,
+ "Oops! We couldn't log you in. Please check your credentials and try again."
+ );
case 402:
- return umbLocalizationContext.localize('login_2faText', undefined, 'You have enabled 2-factor authentication and must verify your identity.');
+ return umbLocalizationContext.localize(
+ 'login_2faText',
+ undefined,
+ 'You have enabled 2-factor authentication and must verify your identity.'
+ );
case 500:
- return umbLocalizationContext.localize('errors_receivedErrorFromServer', undefined, 'Received error from server');
+ return umbLocalizationContext.localize(
+ 'errors_receivedErrorFromServer',
+ undefined,
+ 'Received error from server'
+ );
default:
- return response.statusText ?? await umbLocalizationContext.localize('errors_receivedErrorFromServer', undefined, 'Received error from server')
+ return (
+ response.statusText ??
+ (await umbLocalizationContext.localize(
+ 'errors_receivedErrorFromServer',
+ undefined,
+ 'Received error from server'
+ ))
+ );
}
}
diff --git a/src/Umbraco.Web.UI.Login/src/mocks/data/texts.json b/src/Umbraco.Web.UI.Login/src/mocks/data/texts.json
index 1175a93f75..1762570dc5 100644
--- a/src/Umbraco.Web.UI.Login/src/mocks/data/texts.json
+++ b/src/Umbraco.Web.UI.Login/src/mocks/data/texts.json
@@ -1,15 +1,34 @@
{
- "general": {
- "login": "Login"
- },
- "user": {
- "username": "Username",
- "email": "Email",
- "password": "Password"
- },
- "placeholders": {
- "username": "Enter your username",
- "email": "Enter your email",
- "password": "Enter your password"
- }
+ "general": {
+ "login": "Login",
+ "email": "Email",
+ "username": "Username",
+ "password": "Password",
+ "welcome": "Welcome",
+ "or": "Or",
+ "validate": "Validate",
+ "back": "Back",
+ "required": "Required"
+ },
+ "login": {
+ "forgottenPassword": "Forgot password?",
+ "signInWith": "Sign in with",
+ "forgottenPasswordInstruction": "Enter your email address and we'll send you a link to reset your password.",
+ "returnToLogin": "Return to login",
+ "2faTitle": "Two-factor authentication",
+ "2faText": "Enter the code from your authenticator app",
+ "2faMultipleText": "Select an authentication method",
+ "2faCodeInput": "Enter your code",
+ "2faCodeInputHelp": "Enter the code from your authenticator app"
+ },
+ "user": {
+ "username": "Username",
+ "email": "Email",
+ "password": "Password"
+ },
+ "placeholders": {
+ "username": "Enter your username",
+ "email": "Enter your email",
+ "password": "Enter your password"
+ }
}
diff --git a/src/Umbraco.Web.UI.Login/src/mocks/handlers/login.handlers.ts b/src/Umbraco.Web.UI.Login/src/mocks/handlers/login.handlers.ts
index aef36f517a..9eb516c126 100644
--- a/src/Umbraco.Web.UI.Login/src/mocks/handlers/login.handlers.ts
+++ b/src/Umbraco.Web.UI.Login/src/mocks/handlers/login.handlers.ts
@@ -38,8 +38,9 @@ export const handlers = [
rest.post('backoffice/umbracoapi/authentication/PostVerify2faCode', async (req, res, ctx) => {
const body = await req.json();
if (body.code === 'fail') {
- return res(ctx.delay(), ctx.status(400), ctx.json({ message: 'Invalid code' }));
+ return res(ctx.delay(), ctx.status(400), ctx.json({ Message: 'Invalid code' }));
}
- return res(ctx.delay(), ctx.status(200));
+ const user = umbLoginData.users[0];
+ return res(ctx.delay(), ctx.status(200), ctx.json(user));
}),
];