Correct bunch of issues #36
12
.vscode/extensions.json
vendored
12
.vscode/extensions.json
vendored
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -69,7 +69,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.slider {
|
.slider {
|
||||||
padding: 0 20px;
|
margin-right: 20px;
|
||||||
align-content: center;
|
align-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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>
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
|||||||
@@ -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);
|
||||||
@@ -11,23 +11,22 @@
|
|||||||
|
|
||||||
.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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.header-content {
|
.header-content {
|
||||||
|
margin: 0 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|
||||||
@@ -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 },
|
||||||
@@ -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 {
|
||||||
@@ -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>
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
<div class="values">
|
<span class="value">@Value</span>
|
||||||
<span class="value">@Params.Min</span>
|
|
||||||
<span class="value">@Params.Max</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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});";
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
7
src/GameIdeas/GameIdeas.Shared/Dto/MinMaxDto.cs
Normal file
7
src/GameIdeas/GameIdeas.Shared/Dto/MinMaxDto.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace GameIdeas.Shared.Dto;
|
||||||
|
|
||||||
|
public class MinMaxDto
|
||||||
|
{
|
||||||
|
public int? Min { get; set; }
|
||||||
|
public int? Max { get; set; }
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user