Finish base header

This commit is contained in:
2025-02-27 00:56:30 +01:00
parent 1ad958a58c
commit f5fc908f53
15 changed files with 263 additions and 51 deletions

View File

@@ -8,6 +8,8 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Blazored.FluentValidation" Version="2.2.0" />
<PackageReference Include="FluentValidation" Version="11.11.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.2" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.2" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.2" /> <PackageReference Include="Microsoft.Extensions.Http" Version="9.0.2" />
@@ -15,6 +17,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\GameIdeas.Resources\GameIdeas.Resources.csproj" /> <ProjectReference Include="..\..\GameIdeas.Resources\GameIdeas.Resources.csproj" />
<ProjectReference Include="..\..\GameIdeas.Shared\GameIdeas.Shared.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -2,7 +2,6 @@ namespace GameIdeas.BlazorApp.Pages.Games;
public enum AccountSetting public enum AccountSetting
{ {
Login,
Logout Logout
} }

View File

@@ -1,5 +1,6 @@
using System.Net.Http.Json; using System.Net.Http.Json;
using GameIdeas.BlazorApp; using GameIdeas.BlazorApp;
using GameIdeas.BlazorApp.Services;
using GameIdeas.Resources; using GameIdeas.Resources;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
@@ -21,6 +22,8 @@ builder.Services.AddHttpClient(
client.Timeout = TimeSpan.FromMinutes(3); client.Timeout = TimeSpan.FromMinutes(3);
}); });
builder.Services.AddScoped<AuthentificationService>();
builder.Services.AddSingleton<TranslationService>(); builder.Services.AddSingleton<TranslationService>();
builder.Services.AddSingleton<Translations>(); builder.Services.AddSingleton<Translations>();

View File

@@ -0,0 +1,21 @@
namespace GameIdeas.BlazorApp.Services;
public class AuthentificationService
{
private bool isLogin;
public bool IsLogin
{
get { return isLogin; }
}
public void Login()
{
isLogin = true;
}
public void Logout()
{
isLogin = false;
}
}

View File

@@ -5,7 +5,8 @@
border-radius: var(--small-radius); border-radius: var(--small-radius);
position: fixed; position: fixed;
animation-name: fade-in; animation-name: fade-in;
animation-duration: 0.4s animation-duration: 0.4s;
margin-top: 4px;
} }
.drowdown-element { .drowdown-element {

View File

@@ -1,6 +1,46 @@
<div @ref=Content class="account-setting-container" tabindex="1000" @using GameIdeas.Resources
@onfocusout=HandleAccountSettingFocusOut> @using Blazored.FluentValidation;
<div class="account-setting-content @(ContentVisile ? string.Empty : "invisible"))">
<div class="account-setting-container" tabindex="1001">
<div class="account-setting-content @(ContentVisile ? string.Empty : "invisible")">
@if (!AuthentificationService.IsLogin)
{
<EditForm EditContext="EditContext" OnSubmit="HandleLoginSubmit">
<FluentValidationValidator />
<div class="login-form">
<div class="login-field">
<div class="input-title">@ResourcesKey.EnterUsername</div>
<InputText class="input-text"
@bind-Value="LoginDto.Username" />
</div>
<div class="login-field">
<div class="input-title">@ResourcesKey.EnterPassword</div>
<InputText class="input-text"
@bind-Value="LoginDto.Password" />
</div>
<div class="login-field">
<button class="login-button" type="submit" disabled="@IsLoading">
@if (IsLoading)
{
<div class="loading"></div>
}
else
{
@ResourcesKey.Login
}
</button>
</div>
</div>
</EditForm>
}
else
{
<div class="settings-list">
<div class="settings-element" @onclick="HandleLogoutClicked">
@ResourcesKey.Logout
</div>
@* <span class="line"></span> *@
</div>
}
</div> </div>
</div> </div>

View File

@@ -1,27 +1,59 @@
using Microsoft.AspNetCore.Components; using GameIdeas.BlazorApp.Services;
using GameIdeas.Shared.Dto;
using Microsoft.AspNetCore.Components.Forms;
namespace GameIdeas.BlazorApp.Shared.Headers; namespace GameIdeas.BlazorApp.Shared.Headers;
public partial class AccountSettings public partial class AccountSettings (
AuthentificationService AuthentificationService)
{ {
private bool ContentVisile = false; private bool ContentVisile = false;
private DateTime ContentLastFocusOut = DateTime.Now; private EditContext? EditContext;
private ElementReference Content; private LoginDto LoginDto = new();
private bool IsLoading = false;
public async Task OpenAsync() protected override void OnInitialized()
{ {
if (DateTime.Now - ContentLastFocusOut >= TimeSpan.FromSeconds(0.2)) EditContext = new EditContext(LoginDto);
{
await Content.FocusAsync();
ContentVisile = true;
}
} }
public void Close() => ContentVisile = false; public void Open()
{
private void HandleAccountSettingFocusOut() ContentVisile = true;
StateHasChanged();
}
public void Close()
{ {
ContentLastFocusOut = DateTime.Now;
ContentVisile = false; ContentVisile = false;
StateHasChanged();
}
public void Toggle()
{
ContentVisile = !ContentVisile;
StateHasChanged();
}
private async Task HandleLoginSubmit()
{
if (EditContext?.Validate() == false)
{
LoginDto.Password = string.Empty;
return;
}
IsLoading = true;
await Task.Delay(TimeSpan.FromSeconds(5));
IsLoading = false;
LoginDto = new();
Close();
AuthentificationService.Login();
}
private void HandleLogoutClicked()
{
Close();
AuthentificationService.Logout();
} }
} }

View File

@@ -1,13 +1,94 @@
.account-setting-content { .account-setting-content {
overflow: hidden; overflow: hidden;
display: flex;
flex-direction: column;
border-radius: var(--big-radius); border-radius: var(--big-radius);
position: fixed; position: fixed;
animation-name: fade-in; animation-name: fade-in;
animation-duration: 0.4s animation-duration: 0.4s;
border: 2px solid var(--light-grey);
background: var(--black);
right: 10px;
margin-top: 4px;
} }
.invisible { .invisible {
display: none; display: none;
} }
.login-form {
display: flex;
flex-direction: column;
padding: 20px 6px;
gap: 20px;
max-width: 400px;
}
.login-field {
display: flex;
flex-direction: column;
width: 100%;
height: fit-content;
}
::deep .input-text {
background: var(--light-grey);
border: 2px solid rgb(255, 255, 255, 0.3);
border-radius: var(--small-radius);
padding: 6px;
color: var(--white);
}
::deep .input-text:focus-visible {
border: 2px solid var(--violet) !important;
}
.login-button {
background: var(--violet);
border: none;
border-radius: 100px;
height: 32px;
color: var(--white);
font-weight: bold;
}
.login-button:hover {
background: var(--violet-selected);
cursor:pointer;
}
.login-button:disabled {
background: var(--violet-selected);
cursor: wait;
}
.loading {
width: 18px;
height: 18px;
border-radius: 50%;
border: 3px solid var(--line-black);
border-top-color: var(--white);
animation: loading 1s linear infinite;
justify-self: center;
}
.settings-list {
display: flex;
flex-direction: column;
}
.line {
margin: 0 6px;
border-bottom: 2px solid var(--light-grey);
}
.settings-element {
max-width: 140px;
height: 40px;
padding: 0 26px;
align-content: center;
}
.settings-element:hover {
background: var(--light-grey)
}

View File

@@ -31,11 +31,12 @@
Theme="DropdownTheme.Navigation" /> Theme="DropdownTheme.Navigation" />
</div> </div>
<div class="account-container"> <div class="account-container">
<div class="icon-container"> <div class="icon-container" @onclick="HandleAccountClicked">
<svg class="account-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="account-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" /> <path d="M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z" />
</svg> </svg>
</div> </div>
<AccountSettings @ref="AccountSettings" />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -17,19 +17,30 @@ public partial class HeaderBase
new AddTypeParams(AddType.Auto, ResourcesKey.AutoAdd) new AddTypeParams(AddType.Auto, ResourcesKey.AutoAdd)
]; ];
private AccountSettings? AccountSettings;
private void HandleIconClicked() private void HandleIconClicked()
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
private async Task HandleMoreButton() private async Task HandleMoreButton()
{ {
if (DropdownAdd != null) if (DropdownAdd != null)
{ {
await DropdownAdd.OpenAsync(); await DropdownAdd.OpenAsync();
} }
AccountSettings?.Close();
} }
private async Task HandleAddTypeClickedAsync(AddTypeParams value) private async Task HandleAddTypeClickedAsync(AddTypeParams value)
{ {
await AddTypeChanged.InvokeAsync(value.AddType); await AddTypeChanged.InvokeAsync(value.AddType);
} }
private void HandleAccountClicked()
{
AccountSettings?.Toggle();
}
} }

View File

@@ -0,0 +1,18 @@
using FluentValidation;
using GameIdeas.Shared.Dto;
namespace GameIdeas.BlazorApp.Shared.Headers;
public class LoginValidator : AbstractValidator<LoginDto>
{
public LoginValidator()
{
RuleFor(dto => dto.Username)
.NotNull()
.NotEmpty();
RuleFor(dto => dto.Password)
.NotNull()
.NotEmpty();
}
}

View File

@@ -3,6 +3,7 @@
:root { :root {
--background: linear-gradient(180deg, #2B1D3D 0%, #171229 100%); --background: linear-gradient(180deg, #2B1D3D 0%, #171229 100%);
--violet: #A380D1; --violet: #A380D1;
--violet-selected: #8266a7;
--red: #FF5E51; --red: #FF5E51;
--yellow: #FFC107; --yellow: #FFC107;
--green: #43F8C0; --green: #43F8C0;
@@ -30,38 +31,16 @@ html, body, #app {
overflow: hidden; overflow: hidden;
} }
h1:focus {
outline: none;
}
a, .btn-link {
color: #0071c1;
}
.btn-primary {
color: #fff;
background-color: #1b6ec2;
border-color: #1861ac;
}
.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb;
}
.content {
padding-top: 1.1rem;
}
.valid.modified:not([type=checkbox]) { .valid.modified:not([type=checkbox]) {
outline: 1px solid #26b050; border: 2px solid var(--green);
} }
.invalid { .invalid {
outline: 1px solid red; border: 2px solid var(--red) !important;
} }
.validation-message { .validation-message {
color: red; color: var(--red);
} }
#blazor-error-ui { #blazor-error-ui {
@@ -142,7 +121,17 @@ code {
text-align: start; text-align: start;
} }
:focus-visible {
outline: none;
}
@keyframes fade-in { @keyframes fade-in {
0% {opacity: 0} 0% {opacity: 0}
100% {opacity: 1} 100% {opacity: 1}
} }
@keyframes loading {
to {
transform: rotate(360deg);
}
}

View File

@@ -8,6 +8,8 @@ public class Translations (TranslationService translationService)
public string AutoAdd => translationService.Translate(nameof(AutoAdd)); public string AutoAdd => translationService.Translate(nameof(AutoAdd));
public string Login => translationService.Translate(nameof(Login)); public string Login => translationService.Translate(nameof(Login));
public string Logout => translationService.Translate(nameof(Logout)); public string Logout => translationService.Translate(nameof(Logout));
public string EnterUsername => translationService.Translate(nameof(EnterUsername));
public string EnterPassword => translationService.Translate(nameof(EnterPassword));
} }
public static class ResourcesKey public static class ResourcesKey
@@ -24,4 +26,6 @@ public static class ResourcesKey
public static string AutoAdd => _instance?.AutoAdd ?? throw new InvalidOperationException("ResourcesKey.AutoAdd is not initialized."); public static string AutoAdd => _instance?.AutoAdd ?? throw new InvalidOperationException("ResourcesKey.AutoAdd is not initialized.");
public static string Login => _instance?.Login ?? throw new InvalidOperationException("ResourcesKey.Login is not initialized."); public static string Login => _instance?.Login ?? throw new InvalidOperationException("ResourcesKey.Login is not initialized.");
public static string Logout => _instance?.Logout ?? throw new InvalidOperationException("ResourcesKey.Logout is not initialized."); public static string Logout => _instance?.Logout ?? throw new InvalidOperationException("ResourcesKey.Logout is not initialized.");
public static string EnterUsername => _instance?.EnterUsername ?? throw new InvalidOperationException("ResourcesKey.EnterUsername is not initialized.");
public static string EnterPassword => _instance?.EnterPassword ?? throw new InvalidOperationException("ResourcesKey.EnterPassword is not initialized.");
} }

View File

@@ -0,0 +1,7 @@
namespace GameIdeas.Shared.Dto;
public class LoginDto
{
public string? Username { get; set; }
public string? Password { get; set; }
}

View File

@@ -2,6 +2,8 @@
"GamesIdeas": "Game Ideas", "GamesIdeas": "Game Ideas",
"ManualAdd": "Manuel", "ManualAdd": "Manuel",
"AutoAdd": "Automatique", "AutoAdd": "Automatique",
"Login": "Connexion", "Login": "Se connecter",
"Logout": "Déconnexion" "Logout": "Se déconnecter",
"EnterUsername": "Nom d'utilisateur",
"EnterPassword": "Mot de passe"
} }