Correct bunch of issues #36

Merged
Egamorf merged 7 commits from fix/multiple-issues-from-gitea into main 2025-04-29 23:49:11 +02:00
37 changed files with 246 additions and 186 deletions

View File

View File

@@ -1,12 +0,0 @@
{
"recommendations": [
"ms-dotnettools.vscode-dotnet-runtime",
"ms-dotnettools.csharp",
"ms-dotnettools.csdevkit",
"kreativ-software.csharpextensions",
"jorgeserrano.vscode-csharp-snippets",
"mhutchie.git-graph",
"lukas-tr.materialdesignicons-intellisense",
"ms-dotnettools.vscodeintellicode-csharp"
]
}

View File

@@ -19,7 +19,9 @@ Store your favorite games, intelligent game add, store game files and data, mana
. .
├── README.md ├── README.md
├── .gitignore ├── .gitignore
├── .drone.yml (CI/CD) ├── .gitea
│ ├── build-pr.yaml (CI for Pull Request)
│ └── deploy.yaml (CD for deploy on own server)
└── src/ └── src/
├── Client/ ├── Client/
│ └── GameIdeas.BlazorApp │ └── GameIdeas.BlazorApp

View File

@@ -1,10 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>5637e3c4-2341-4bdb-85ec-c75faeee9847</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@@ -69,7 +69,7 @@
} }
.slider { .slider {
padding: 0 20px; margin-right: 20px;
align-content: center; align-content: center;
} }

View File

@@ -1,5 +1,4 @@
using GameIdeas.BlazorApp.Helpers; using GameIdeas.BlazorApp.Helpers;
using GameIdeas.BlazorApp.Pages.Games.Header;
using GameIdeas.BlazorApp.Shared.Components.Select.Models; using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using GameIdeas.Resources; using GameIdeas.Resources;
using GameIdeas.Shared.Dto; using GameIdeas.Shared.Dto;

View File

@@ -47,8 +47,7 @@
<div class="slider-container"> <div class="slider-container">
<SliderRange Params="SliderRangeParams" <SliderRange Params="SliderRangeParams"
@bind-Max="Value.MaxInterest" @bind-Max:after="HandleValueChanged" @bind-Value="Value.Interest" @bind-Value:after="HandleValueChanged" />
@bind-Min="Value.MinInterest" @bind-Min:after="HandleValueChanged" />
</div> </div>
</div> </div>

View File

@@ -2,7 +2,7 @@
display: flex; display: flex;
flex-direction: row; flex-direction: row;
gap: 8px; gap: 8px;
margin: 0 8px; margin: 0 auto;
align-items: center; align-items: center;
} }

View File

@@ -12,8 +12,7 @@ public class GameFilterParams
public List<TagDto>? Tags { get; set; } public List<TagDto>? Tags { get; set; }
public List<PublisherDto>? Publishers { get; set; } public List<PublisherDto>? Publishers { get; set; }
public List<DeveloperDto>? Developers { get; set; } public List<DeveloperDto>? Developers { get; set; }
public int MinInterest { get; set; } = 1; public MinMaxDto Interest { get; set; } = new() { Min = 1, Max = 5 };
public int MaxInterest { get; set; } = 5;
public List<int>? ReleaseYears { get; set; } public List<int>? ReleaseYears { get; set; }
public List<int>? StorageSpaceIds { get; set; } public List<int>? StorageSpaceIds { get; set; }
} }

View File

@@ -1,9 +1,10 @@
@page "/Games" @page "/"
@using GameIdeas.BlazorApp.Layouts @using GameIdeas.BlazorApp.Layouts
@using GameIdeas.BlazorApp.Pages.Games.Components @using GameIdeas.BlazorApp.Pages.Games.Components
@using GameIdeas.BlazorApp.Pages.Games.Filter @using GameIdeas.BlazorApp.Pages.Games.Filter
@using GameIdeas.BlazorApp.Pages.Games.Header
@using GameIdeas.BlazorApp.Shared.Components @using GameIdeas.BlazorApp.Shared.Components
@using GameIdeas.BlazorApp.Shared.Components.ButtonAdd
@using GameIdeas.BlazorApp.Shared.Components.Header
@using GameIdeas.BlazorApp.Shared.Components.Popup @using GameIdeas.BlazorApp.Shared.Components.Popup
@using GameIdeas.Resources @using GameIdeas.Resources
@@ -11,11 +12,12 @@
<PageTitle>@ResourcesKey.GamesIdeas</PageTitle> <PageTitle>@ResourcesKey.GamesIdeas</PageTitle>
<GameHeader AddTypeChanged="HandleAddClicked"> <HeaderGameIdeas>
<GameFilter Categories="Categories" <GameFilter Categories="Categories"
@bind-DisplayType=DisplayType @bind-DisplayType=DisplayType
Value=GameFilter ValueChanged="HandleFilterChanged" /> Value=GameFilter ValueChanged="HandleFilterChanged" />
</GameHeader> <ButtonAdd AddTypeChanged="HandleAddClicked" />
</HeaderGameIdeas>
<div class="container"> <div class="container">
<div class="content"> <div class="content">

View File

@@ -43,8 +43,8 @@ public class GameGateway(IHttpClientService httpClientService) : IGameGateway
{ {
CurrentPage = currentPage, CurrentPage = currentPage,
Title = filterParams.Title, Title = filterParams.Title,
MaxInterest = filterParams.MaxInterest, MaxInterest = filterParams.Interest.Max,
MinInterest = filterParams.MinInterest, MinInterest = filterParams.Interest.Min,
StorageSpaces = filterParams.StorageSpaceIds, StorageSpaces = filterParams.StorageSpaceIds,
DeveloperIds = filterParams.Developers?.Select(d => d.Id ?? 0).ToList(), DeveloperIds = filterParams.Developers?.Select(d => d.Id ?? 0).ToList(),
PublisherIds = filterParams.Publishers?.Select(d => d.Id ?? 0).ToList(), PublisherIds = filterParams.Publishers?.Select(d => d.Id ?? 0).ToList(),

View File

@@ -1,45 +0,0 @@
@using GameIdeas.BlazorApp.Pages.Games
@using GameIdeas.BlazorApp.Pages.UserMenu
@using GameIdeas.BlazorApp.Shared.Components.Select
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
@using GameIdeas.BlazorApp.Shared.Models
@using GameIdeas.Resources
@using GameIdeas.Shared.Constants
@using Microsoft.AspNetCore.Components.Authorization
@inherits ComponentBase
<div class="header-tab">
<a href="/Games" class="icon-container">
<img src="icon.png" alt="Game Ideas">
</a>
@ChildContent
<div class="account-add-container">
@if (DisplayAdd)
{
<AuthorizeView Roles="@GlobalConstants.ADMIN_MEMBER">
<Authorized>
<div class="add-buttons">
<div class="first-button button">
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</svg>
</div>
<Select @ref="SelectListAdd" TItem="KeyValuePair<AddType, string>" THeader="object"
ValuesChanged=HandleAddTypeClicked Params=SelectParams Theme="SelectTheme.Navigation">
<div class="second-button button">
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M1 3H23L12 22" />
</svg>
</div>
</Select>
</div>
</Authorized>
</AuthorizeView>
}
<UserMenu />
</div>
</div>

View File

@@ -14,8 +14,10 @@ public class AuthGateway(IHttpClientService httpClient,
{ {
try try
{ {
var token = await httpClient.PostAsync<TokenDto>(Endpoints.Auth.Login, userDto); var token = await httpClient.PostAsync<TokenDto>(Endpoints.Auth.Login, userDto)
await ((JwtAuthenticationStateProvider)stateProvider).NotifyUserAuthenticationAsync(token!.Token!); ?? throw new InvalidOperationException("Could not retrieve token");
await ((JwtAuthenticationStateProvider)stateProvider).NotifyUserAuthenticationAsync(token);
return true; return true;
} }
catch (Exception) catch (Exception)

View File

@@ -14,7 +14,7 @@ public partial class UserMenu
{ {
ContentVisile = false; ContentVisile = false;
await AuthGateway.Logout(); await AuthGateway.Logout();
NavigationManager.NavigateTo("/Games"); NavigationManager.NavigateTo("/");
} }
private void HandleAccountClicked() private void HandleAccountClicked()

View File

@@ -23,7 +23,10 @@
</Select> </Select>
</div> </div>
<div class="buttons"> <div class="buttons">
@if (CanDelete)
{
<button type="button" class="remove" @onclick="HandleRemoveClicked">@Icons.Bin</button> <button type="button" class="remove" @onclick="HandleRemoveClicked">@Icons.Bin</button>
}
@if (CanEdit) @if (CanEdit)
{ {
<button type="button" class="edit @(IsEditing ? "selected" : "")" @onclick="HandleEditClicked">@Icons.Pen</button> <button type="button" class="edit @(IsEditing ? "selected" : "")" @onclick="HandleEditClicked">@Icons.Pen</button>

View File

@@ -12,6 +12,7 @@ public partial class UserRow
[Parameter] public UserDto User { get; set; } = new(); [Parameter] public UserDto User { get; set; } = new();
[Parameter] public List<RoleDto> Roles { get; set; } = []; [Parameter] public List<RoleDto> Roles { get; set; } = [];
[Parameter] public bool CanEdit { get; set; } = true; [Parameter] public bool CanEdit { get; set; } = true;
[Parameter] public bool CanDelete { get; set; } = true;
[Parameter] public bool IsEditing { get; set; } = false; [Parameter] public bool IsEditing { get; set; } = false;
[Parameter] public EventCallback<UserDto> OnRemove { get; set; } [Parameter] public EventCallback<UserDto> OnRemove { get; set; }
[Parameter] public EventCallback<UserDto> OnSubmit { get; set; } [Parameter] public EventCallback<UserDto> OnSubmit { get; set; }

View File

@@ -1,7 +1,7 @@
.row { .row {
height: 64px; height: 64px;
display: grid; display: grid;
grid-template-columns: 48px 1fr 1fr 1fr auto; grid-template-columns: 48px 1fr 1fr 1fr 100px;
grid-gap: 8px; grid-gap: 8px;
padding: 0 8px; padding: 0 8px;
background: var(--input-secondary); background: var(--input-secondary);
@@ -9,25 +9,24 @@
border-radius: var(--big-radius); border-radius: var(--big-radius);
} }
.row > * { .row > * {
align-content: center; align-content: center;
} max-width: 160px;
width: 100%;
}
.icon ::deep svg { .icon ::deep svg {
fill: var(--line); fill: var(--line);
} }
.role {
min-width: 160px;
width: fit-content;
}
::deep .input-name, ::deep .input-name,
::deep .input-name[disabled], ::deep .input-name[disabled],
::deep .input-password, ::deep .input-password,
::deep .input-password[disabled], ::deep .input-password[disabled],
::deep .input-password::placeholder { ::deep .input-password::placeholder {
color: var(--white); color: var(--white);
max-width: 160px;
width: 100%;
} }
::deep .input-name, ::deep .input-name,
@@ -61,12 +60,13 @@
flex-direction: row; flex-direction: row;
gap: 8px; gap: 8px;
height:auto; height:auto;
justify-content: end;
} }
.buttons > * { .buttons > * {
border: none; border: none;
outline: none; outline: none;
margin: auto; margin: auto 0;
height: 28px; height: 28px;
width: 28px; width: 28px;
background: none; background: none;

View File

@@ -1,7 +1,7 @@
@page "/Users" @page "/Users"
@using GameIdeas.BlazorApp.Pages.Games.Header
@using GameIdeas.BlazorApp.Layouts @using GameIdeas.BlazorApp.Layouts
@using GameIdeas.BlazorApp.Pages.Users.Components @using GameIdeas.BlazorApp.Pages.Users.Components
@using GameIdeas.BlazorApp.Shared.Components.Header
@using GameIdeas.BlazorApp.Shared.Components.Popup @using GameIdeas.BlazorApp.Shared.Components.Popup
@using GameIdeas.BlazorApp.Shared.Components.Popup.Components @using GameIdeas.BlazorApp.Shared.Components.Popup.Components
@using GameIdeas.BlazorApp.Shared.Components.Search @using GameIdeas.BlazorApp.Shared.Components.Search
@@ -13,13 +13,13 @@
<PageTitle>@ResourcesKey.GamesIdeas</PageTitle> <PageTitle>@ResourcesKey.GamesIdeas</PageTitle>
<GameHeader DisplayAdd="false"> <HeaderGameIdeas>
<div class="header-content"> <div class="header-content">
<SearchInput Placeholder="@ResourcesKey.EnterUsername" @bind-Text="FilterParams.Name" @bind-Text:after=HandleFilterChanged /> <SearchInput Placeholder="@ResourcesKey.EnterUsername" @bind-Text="FilterParams.Name" @bind-Text:after=HandleFilterChanged />
<SelectSearch TItem="RoleDto" Placeholder="@ResourcesKey.Roles" @bind-Values="FilterParams.Roles" @bind-Values:after=HandleFilterChanged <SelectSearch TItem="RoleDto" Placeholder="@ResourcesKey.Roles" @bind-Values="FilterParams.Roles" @bind-Values:after=HandleFilterChanged
Items="Roles.ToList()" GetLabel="@(role => role.Name)" Theme="SelectTheme.Filter" /> Items="Roles.ToList()" GetLabel="@(role => role.Name)" Theme="SelectTheme.Filter" />
</div> </div>
</GameHeader> </HeaderGameIdeas>
<div class="container"> <div class="container">
<UserRow User="UserAdd" Roles="Roles.ToList()" CanEdit="false" IsEditing="true" OnRemove="HandleResetUser" OnSubmit="HandleSubmitUser" Validator="@(new UserCreateValidator())" /> <UserRow User="UserAdd" Roles="Roles.ToList()" CanEdit="false" IsEditing="true" OnRemove="HandleResetUser" OnSubmit="HandleSubmitUser" Validator="@(new UserCreateValidator())" />
@@ -31,7 +31,7 @@
{ {
@foreach (var user in UserList.Users ?? []) @foreach (var user in UserList.Users ?? [])
{ {
<UserRow User="user" Roles="Roles.ToList()" OnRemove="HandleOpenConfirmationPopup" OnSubmit="HandleUpdateUser" Validator="@(new UserUpdateValidator())" /> <UserRow User="user" Roles="Roles.ToList()" OnRemove="HandleOpenConfirmationPopup" OnSubmit="HandleUpdateUser" Validator="@(new UserUpdateValidator())" CanDelete=@(user.Id != currentUserId) />
} }
} }
else else

View File

@@ -1,14 +1,19 @@
using GameIdeas.BlazorApp.Pages.Users.Filters; using GameIdeas.BlazorApp.Pages.Users.Filters;
using GameIdeas.BlazorApp.Pages.Users.Gateways; using GameIdeas.BlazorApp.Pages.Users.Gateways;
using GameIdeas.BlazorApp.Shared.Components.Popup; using GameIdeas.BlazorApp.Shared.Components.Popup;
using GameIdeas.Shared.Constants;
using GameIdeas.Shared.Dto; using GameIdeas.Shared.Dto;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims;
namespace GameIdeas.BlazorApp.Pages.Users; namespace GameIdeas.BlazorApp.Pages.Users;
public partial class Users public partial class Users
{ {
[Inject] private IUserGateway UserGateway { get; set; } = default!; [Inject] private IUserGateway UserGateway { get; set; } = default!;
[Inject] private AuthenticationStateProvider StateProvider { get; set; } = default!;
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
private Popup? Popup; private Popup? Popup;
private bool IsLoading = false; private bool IsLoading = false;
@@ -18,9 +23,19 @@ public partial class Users
private int CurrentPage = 1; private int CurrentPage = 1;
private UserDto UserAdd = new(); private UserDto UserAdd = new();
private UserDto? UserDelete; private UserDto? UserDelete;
private string? currentUserId;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
var authState = await StateProvider.GetAuthenticationStateAsync();
currentUserId = authState.User.FindFirstValue(ClaimTypes.Sid);
if (authState.User.FindFirstValue(ClaimTypes.Role) != GlobalConstants.ADMINISTRATOR || string.IsNullOrEmpty(currentUserId))
{
NavigationManager.NavigateTo("/Unauthorized");
return;
}
await FetchData(); await FetchData();
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }

View File

@@ -1,6 +1,6 @@
.header-content { .header-content {
margin: 0 auto;
display: flex; display: flex;
flex-direction: row;
gap: 8px; gap: 8px;
} }

View File

@@ -7,7 +7,7 @@
"launchBrowser": true, "launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "http://localhost:5172", "applicationUrl": "http://localhost:5172",
"launchUrl": "http://localhost:5172/Games", "launchUrl": "http://localhost:5172/",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
@@ -18,7 +18,7 @@
"launchBrowser": true, "launchBrowser": true,
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://localhost:7060;http://localhost:5172", "applicationUrl": "https://localhost:7060;http://localhost:5172",
"launchUrl": "http://localhost:7060/Games", "launchUrl": "http://localhost:7060/",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

View File

@@ -5,13 +5,15 @@ using System.Text.Json;
using System.Text; using System.Text;
using Blazored.LocalStorage; using Blazored.LocalStorage;
using GameIdeas.Shared.Constants; using GameIdeas.Shared.Constants;
using Microsoft.AspNetCore.Components.Authorization;
namespace GameIdeas.BlazorApp.Services; namespace GameIdeas.BlazorApp.Services;
public class HttpClientService( public class HttpClientService(
IHttpClientFactory httpClientFactory, IHttpClientFactory httpClientFactory,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
ILocalStorageService localStorage) : IHttpClientService ILocalStorageService localStorage,
AuthenticationStateProvider stateProvider) : IHttpClientService
{ {
private readonly HttpClient httpClient = httpClientFactory.CreateClient("GameIdeas.WebAPI"); private readonly HttpClient httpClient = httpClientFactory.CreateClient("GameIdeas.WebAPI");
private readonly ILogger<HttpClientService> logger = loggerFactory.CreateLogger<HttpClientService>(); private readonly ILogger<HttpClientService> logger = loggerFactory.CreateLogger<HttpClientService>();
@@ -141,6 +143,16 @@ public class HttpClientService(
private async Task SetAuthorizationHeader() private async Task SetAuthorizationHeader()
{ {
var expired = await localStorage.GetItemAsStringAsync(GlobalConstants.LS_EXPIRED_STORAGE_KEY);
if (expired == null
|| (DateTime.TryParse(expired, out DateTime expiration)
&& expiration < DateTime.UtcNow))
{
await ((JwtAuthenticationStateProvider)stateProvider).NotifyUserLogoutAsync();
return;
}
var token = await localStorage.GetItemAsStringAsync(GlobalConstants.LS_AUTH_STORAGE_KEY); var token = await localStorage.GetItemAsStringAsync(GlobalConstants.LS_AUTH_STORAGE_KEY);
httpClient.DefaultRequestHeaders.Authorization = httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("bearer", token); new AuthenticationHeaderValue("bearer", token);

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Components.Authorization;
using System.Security.Claims; using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt; using System.IdentityModel.Tokens.Jwt;
using GameIdeas.Shared.Constants; using GameIdeas.Shared.Constants;
using GameIdeas.Shared.Dto;
namespace GameIdeas.BlazorApp.Services; namespace GameIdeas.BlazorApp.Services;
@@ -31,9 +32,17 @@ public class JwtAuthenticationStateProvider(ILocalStorageService localStorage) :
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
} }
public async Task NotifyUserAuthenticationAsync(string token) public async Task NotifyUserAuthenticationAsync(TokenDto token)
{ {
await localStorage.SetItemAsStringAsync(GlobalConstants.LS_AUTH_STORAGE_KEY, token); if (token?.Token != null)
{
await localStorage.SetItemAsStringAsync(GlobalConstants.LS_AUTH_STORAGE_KEY, token.Token);
}
if (token?.Expiration != null)
{
await localStorage.SetItemAsStringAsync(GlobalConstants.LS_EXPIRED_STORAGE_KEY, token.Expiration.Value.ToString());
}
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
} }

View File

@@ -0,0 +1,27 @@
@using GameIdeas.BlazorApp.Shared.Components.Select
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
@using GameIdeas.Shared.Constants
@using GameIdeas.BlazorApp.Shared.Models
@using Microsoft.AspNetCore.Components.Authorization
<AuthorizeView Roles="@GlobalConstants.ADMIN_MEMBER">
<Authorized>
<div class="add-buttons">
<div class="first-button button">
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M19,13H13V19H11V13H5V11H11V5H13V11H19V13Z" />
</svg>
</div>
<Select @ref="SelectListAdd" TItem="KeyValuePair<AddType, string>" THeader="object"
ValuesChanged=HandleAddTypeClicked Params=SelectParams Theme="SelectTheme.Navigation">
<div class="second-button button">
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M1 3H23L12 22" />
</svg>
</div>
</Select>
</div>
</Authorized>
</AuthorizeView>

View File

@@ -1,17 +1,14 @@
using GameIdeas.BlazorApp.Shared.Components.Select;
using GameIdeas.BlazorApp.Shared.Components.Select.Models; using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using GameIdeas.BlazorApp.Shared.Components.Select;
using GameIdeas.BlazorApp.Shared.Models; using GameIdeas.BlazorApp.Shared.Models;
using GameIdeas.Resources; using GameIdeas.Resources;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Pages.Games.Header; namespace GameIdeas.BlazorApp.Shared.Components.ButtonAdd;
public partial class GameHeader : ComponentBase public partial class ButtonAdd
{ {
[Parameter] public bool DisplayAdd { get; set; } = true;
[Parameter] public EventCallback<AddType> AddTypeChanged { get; set; } [Parameter] public EventCallback<AddType> AddTypeChanged { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
private readonly Dictionary<AddType, string> AddTypes = new() { private readonly Dictionary<AddType, string> AddTypes = new() {
{ AddType.Manual, ResourcesKey.ManualAdd }, { AddType.Manual, ResourcesKey.ManualAdd },

View File

@@ -1,40 +1,10 @@
.header-tab { .add-buttons {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
padding: 0px 10px;
height: 40px;
}
.icon-container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 40px;
height: 100%;
cursor: pointer;
}
.icon-container img {
max-height: 85%;
max-width: 85%;
}
.account-add-container {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: flex-end;
}
.add-buttons {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
background: var(--violet); background: var(--violet);
border-radius: var(--small-radius); border-radius: var(--small-radius);
margin-right: 40px; margin-right: 40px;
margin-left: 8px;
} }
.button { .button {

View File

@@ -0,0 +1,22 @@
 @using GameIdeas.BlazorApp.Pages.Games
@using GameIdeas.BlazorApp.Pages.UserMenu
@using GameIdeas.BlazorApp.Shared.Components.Select
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
@using GameIdeas.BlazorApp.Shared.Models
@using GameIdeas.Resources
@using GameIdeas.Shared.Constants
@using Microsoft.AspNetCore.Components.Authorization
@inherits ComponentBase
<div class="header-tab">
<a href="/" class="icon-container">
<img src="icon.png" alt="Game Ideas">
</a>
<div class="content">
@ChildContent
</div>
<UserMenu />
</div>

View File

@@ -0,0 +1,12 @@
using GameIdeas.BlazorApp.Shared.Components.Select;
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using GameIdeas.BlazorApp.Shared.Models;
using GameIdeas.Resources;
using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Header;
public partial class HeaderGameIdeas : ComponentBase
{
[Parameter] public RenderFragment? ChildContent { get; set; }
}

View File

@@ -0,0 +1,28 @@
.header-tab {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
padding: 0px 10px;
height: 40px;
}
.icon-container {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 40px;
height: 100%;
cursor: pointer;
}
.icon-container img {
max-height: 85%;
max-width: 85%;
}
.content {
width: 100%;
display: flex;
}

View File

@@ -1,4 +1,5 @@
using GameIdeas.BlazorApp.Shared.Constants; using GameIdeas.BlazorApp.Shared.Constants;
using GameIdeas.Shared.Constants;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Search; namespace GameIdeas.BlazorApp.Shared.Components.Search;
@@ -21,7 +22,7 @@ public partial class SearchInput
Text = string.Empty; Text = string.Empty;
Timer = new() Timer = new()
{ {
Interval = 500, Interval = GlobalConstants.DELAY_INPUT_MS,
AutoReset = false, AutoReset = false,
}; };

View File

@@ -1,11 +1,10 @@
<div class="container"> <div class="container">
<div class="input">
<div class="slider-track"></div> <div class="slider-track"></div>
<input type="range" id="min-range" style="@StatusColor(Value)" min="@Params.Min" max="@Params.Max" <input type="range" id="min-range" style="@StatusColor(Value)" min="@Params.Min" max="@Params.Max"
@bind="@Value" @bind:event="oninput" @bind:after=HandleSlideOnInput /> @bind="@Value" @bind:event="oninput" @bind:after=HandleSlideOnInput />
<div class="values">
<span class="value">@Params.Min</span>
<span class="value">@Params.Max</span>
</div> </div>
<span class="value">@Value</span>
</div> </div>

View File

@@ -1,7 +1,7 @@
.container { .input {
position: relative;
width: 100%; width: 100%;
z-index: 0 position: relative;
height: 2px;
} }
input[type="range"] { input[type="range"] {
@@ -24,7 +24,6 @@ input[type="range"] {
margin: auto; margin: auto;
border-radius: 2px; border-radius: 2px;
background: var(--input-primary); background: var(--input-primary);
} }
input[type="range"]::-webkit-slider-runnable-track { input[type="range"]::-webkit-slider-runnable-track {
@@ -75,15 +74,13 @@ input[type="range"]::-ms-thumb {
} }
.values { .values {
display: flex;
position: absolute;
margin-top: 2px;
width: 100%;
font-weight: bold; font-weight: bold;
justify-content: space-between; width: 1em;
} }
.value { .container {
width: 1em; gap: 8px;
text-align:center; width: 100%;
display: flex;
align-items: center;
} }

View File

@@ -1,13 +1,13 @@
<div class="container"> <div class="container">
<div class="slider-track" style="@FillColor()"></div> <div class="slider-track" style="@FillColor()"></div>
<input type="range" id="min-range" style="@StatusColor(Min)" min="@Params.Min" max="@Params.Max" <input type="range" id="min-range" style="@StatusColor(Value.Min)" min="@Params.Min" max="@Params.Max"
@bind="@Min" @bind:event="oninput" @bind:after=HandleSlideOnInput /> @bind="@Value.Min" @bind:event="oninput" @bind:after=HandleSlideOnInput />
<input type="range" id="max-range" style="@StatusColor(Max)" min="@Params.Min" max="@Params.Max" <input type="range" id="max-range" style="@StatusColor(Value.Max)" min="@Params.Min" max="@Params.Max"
@bind="@Max" @bind:event="oninput" @bind:after=HandleSlideTwoInput /> @bind="@Value.Max" @bind:event="oninput" @bind:after=HandleSlideTwoInput />
<div class="values"> <div class="values">
<span class="value">@Min</span> <span class="value">@Value.Min</span>
<span class="value">@Max</span> <span class="value">@Value.Max</span>
</div> </div>
</div> </div>

View File

@@ -1,44 +1,58 @@
using GameIdeas.Shared.Constants;
using GameIdeas.Shared.Dto;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using System.Threading.Tasks;
namespace GameIdeas.BlazorApp.Shared.Components.SliderRange; namespace GameIdeas.BlazorApp.Shared.Components.SliderRange;
public partial class SliderRange public partial class SliderRange
{ {
[Parameter] public SliderRangeParams Params { get; set; } = new(); [Parameter] public SliderRangeParams Params { get; set; } = new();
[Parameter] public int Max { get; set; } [Parameter] public MinMaxDto Value { get; set; } = new() { Min = 1, Max = 5 };
[Parameter] public EventCallback<int> MaxChanged { get; set; } [Parameter] public EventCallback<MinMaxDto> ValueChanged { get; set; }
[Parameter] public int Min { get; set; }
[Parameter] public EventCallback<int> MinChanged { get; set; }
private async Task HandleSlideTwoInput() private System.Timers.Timer? Timer;
protected override void OnInitialized()
{ {
if (Max - Min <= Params.Gap) Timer = new()
{ {
Min = Max - Params.Gap; Interval = GlobalConstants.DELAY_INPUT_MS,
AutoReset = false,
};
Timer.Elapsed += async (_, _) => await ValueChanged.InvokeAsync(Value);
base.OnInitialized();
} }
await MaxChanged.InvokeAsync(Max); private void HandleSlideTwoInput()
{
if (Value.Max - Value.Min <= Params.Gap)
{
Value.Min = Value.Max - Params.Gap;
} }
private async Task HandleSlideOnInput() Timer?.Stop();
{ Timer?.Start();
if (Max - Min <= Params.Gap)
{
Max = Min + Params.Gap;
} }
await MinChanged.InvokeAsync(Min); private void HandleSlideOnInput()
{
if (Value.Max - Value.Min <= Params.Gap)
{
Value.Max = Value.Min + Params.Gap;
}
Timer?.Stop();
Timer?.Start();
} }
private string FillColor() private string FillColor()
{ {
var percent1 = (double)(Min - Params.Min) / (Params.Max - Params.Min) * 100; var percent1 = (double?)(Value.Min - Params.Min) / (Params.Max - Params.Min) * 100;
var percent2 = (double)(Max - Params.Min) / (Params.Max - Params.Min) * 100; var percent2 = (double?)(Value.Max - Params.Min) / (Params.Max - Params.Min) * 100;
return $"background: linear-gradient(to right, var(--line) {percent1}% , var(--violet) {percent1}% , var(--violet) {percent2}%, var(--line) {percent2}%)"; return $"background: linear-gradient(to right, var(--line) {percent1}% , var(--violet) {percent1}% , var(--violet) {percent2}%, var(--line) {percent2}%)";
} }
private string StatusColor(int value) private string StatusColor(int? value)
{ {
string str = "--thumb-color: var({0});"; string str = "--thumb-color: var({0});";

View File

@@ -12,13 +12,15 @@ public class GlobalConstants
public const string MEMBER_NORMALIZED = "MEMBRE"; public const string MEMBER_NORMALIZED = "MEMBRE";
public const string ADMIN_MEMBER = $"{ADMINISTRATOR}, {MEMBER}"; public const string ADMIN_MEMBER = $"{ADMINISTRATOR}, {MEMBER}";
public const int JWT_DURATION_HOUR = 12; public const int JWT_DURATION_HOUR = 168;
public const int NUMBER_PER_PAGE = 50; public const int NUMBER_PER_PAGE = 50;
public const string LS_AUTH_STORAGE_KEY = "authToken"; public const string LS_AUTH_STORAGE_KEY = "authToken";
public const string LS_EXPIRED_STORAGE_KEY = "expiredToken";
public const int API_PORT = 8000; public const int API_PORT = 8000;
public const string SUB_DOMAIN_NAME = "api-"; public const string SUB_DOMAIN_NAME = "api-";
}
public const double DELAY_INPUT_MS = 500;
}

View File

@@ -0,0 +1,7 @@
namespace GameIdeas.Shared.Dto;
public class MinMaxDto
{
public int? Min { get; set; }
public int? Max { get; set; }
}

View File

@@ -4,7 +4,6 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>c5ccfd3a-f458-4660-b6c4-81fcc2513737</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>