Add validation and gateway creation
All checks were successful
Game Ideas build for PR / build_blazor_app (pull_request) Successful in 1m14s

This commit is contained in:
Maxime Adler
2025-04-14 16:23:45 +02:00
parent b16d997417
commit 3447fa6eb1
14 changed files with 101 additions and 49 deletions

View File

@@ -1,21 +1,23 @@
@using GameIdeas.BlazorApp.Shared.Components.SelectSearch
@using Blazored.FluentValidation
@using GameIdeas.BlazorApp.Shared.Components.SelectSearch
@using GameIdeas.BlazorApp.Shared.Components.Slider
@using GameIdeas.Shared.Dto
<EditForm EditContext="EditContext" OnSubmit="HandleOnSubmit">
<FluentValidationValidator/>
<div class="game-form">
<div class="container">
<div class="input-game">
<div id="first-label" class="label">@ResourcesKey.Title :</div>
<input type="text" class="title" @bind=GameDto.Title>
<InputText class="title" @bind-Value=GameDto.Title />
</div>
<div class="input-game">
<div class="label">@ResourcesKey.ReleaseDate :</div>
<input type="date" class="date" @bind=GameDto.ReleaseDate>
<InputDate TValue="DateTime?" class="date" @bind-Value=GameDto.ReleaseDate />
</div>
<div class="input-game">
<div class="label">@ResourcesKey.StorageSizeMo :</div>
<input type="number" class="storage" @bind=GameDto.StorageSpace>
<InputNumber TValue="double?" class="storage" @bind-Value=GameDto.StorageSpace />
</div>
<div class="input-game">
<div class="label">@ResourcesKey.Developers :</div>
@@ -61,14 +63,14 @@
{
<div class="input-game">
<div class="label">@platform.Label :</div>
<input type="text" class="url" @bind=platform.Url>
<InputText class="url" @bind-Value=platform.Url />
</div>
}
</div>
</div>
<div class="description-container">
<div id="label-description">@ResourcesKey.Description :</div>
<input type="text" class="description" @bind-value=GameDto.Description>
<InputTextArea class="description" @bind-Value=GameDto.Description />
</div>
<div class="buttons">
<button type="reset" class="cancel" @onclick=HandleOnCancel>

View File

@@ -1,3 +1,4 @@
using GameIdeas.BlazorApp.Pages.Games.Gateways;
using GameIdeas.BlazorApp.Shared.Components.Popup;
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using GameIdeas.BlazorApp.Shared.Components.Slider;
@@ -11,6 +12,7 @@ namespace GameIdeas.BlazorApp.Pages.Games.Components;
public partial class GameCreationForm
{
[Inject] private IJSRuntime Js { get; set; } = default!;
[Inject] private IGameGateway GameGateway { get; set; } = default!;
[CascadingParameter] private Popup? Popup { get; set; }
[Parameter] public CategoriesDto? Categories { get; set; }
@@ -48,6 +50,6 @@ public partial class GameCreationForm
return;
}
await GameGateway.CreateGame(GameDto);
}
}

View File

@@ -24,7 +24,7 @@
grid-column: 1;
}
input {
::deep input, ::deep textarea {
width: 100%;
background: var(--input-secondary);
border: solid 1px var(--input-selected);
@@ -35,11 +35,16 @@ input {
color: var(--white);
}
input[type="date"]::-webkit-calendar-picker-indicator {
::deep input[type="date"]::-webkit-calendar-picker-indicator {
filter: invert(1);
cursor: pointer;
}
::deep textarea {
resize: vertical;
min-height: 140px;
}
.description-container {
margin-top: 8px;
display: grid;

View File

@@ -0,0 +1,25 @@
using FluentValidation;
using GameIdeas.Shared.Constants;
using GameIdeas.Shared.Dto;
namespace GameIdeas.BlazorApp.Pages.Games.Components;
public class GameValidation : AbstractValidator<GameDto>
{
public GameValidation()
{
RuleFor(g => g.Title)
.NotEmpty()
.NotNull()
.Matches(GlobalConstants.RegexName);
RuleFor(g => g.ReleaseDate)
.NotEmpty()
.NotNull();
RuleFor(g => g.Interest)
.NotNull()
.GreaterThanOrEqualTo(1)
.LessThanOrEqualTo(5);
}
}

View File

@@ -8,6 +8,20 @@ namespace GameIdeas.BlazorApp.Pages.Games.Gateways;
public class GameGateway(IHttpClientService httpClientService) : IGameGateway
{
public async Task<GameDto> CreateGame(GameDto game)
{
try
{
var result = await httpClientService.PostAsync<GameDto>(Endpoints.Game.Create, game);
return result ?? throw new InvalidOperationException(ResourcesKey.ErrorCreateGame);
}
catch (Exception)
{
throw new GameCreationException(ResourcesKey.ErrorCreateGame);
}
}
public async Task<CategoriesDto> FetchCategories()
{
try

View File

@@ -5,4 +5,5 @@ namespace GameIdeas.BlazorApp.Pages.Games.Gateways;
public interface IGameGateway
{
Task<CategoriesDto> FetchCategories();
Task<GameDto> CreateGame(GameDto game);
}

View File

@@ -15,4 +15,4 @@
<BackdropFilter @ref="BackdropFilter" OnClick="HandleBackdropFilterClicked" CloseOnClick="@Closable"
AllowBodyScroll="false" Color="BackdropFilterColor.Overlay" />
AllowBodyScroll="true" Color="BackdropFilterColor.Overlay" />

View File

@@ -93,7 +93,7 @@ public partial class Select<TItem, THeader>
}
private async Task HandleSubmitAdd()
{
if (Regex.IsMatch(AddLabel, GlobalConstants.RegexSelectRow) &&
if (Regex.IsMatch(AddLabel, GlobalConstants.RegexName) &&
Params.AddItem != null)
{
Values ??= [];

View File

@@ -4,7 +4,7 @@ public static class Endpoints
{
public static class Game
{
public static readonly string Create = "api/Game/Create";
}
public static class Category

View File

@@ -0,0 +1,3 @@
namespace GameIdeas.BlazorApp.Shared.Exceptions;
public class GameCreationException(string message) : Exception(message);

View File

@@ -41,10 +41,6 @@ html, body, #app {
height: 100%;
}
.valid.modified:not([type=checkbox]) {
border: 2px solid var(--green);
}
.invalid {
border: 2px solid var(--red) !important;
}

View File

@@ -35,6 +35,7 @@ public class Translations (TranslationService translationService)
public string RequestFailedStatusFormat => translationService.Translate(nameof(RequestFailedStatusFormat));
public string ErrorFetchCategories => translationService.Translate(nameof(ErrorFetchCategories));
public string PlaceholderAdd => translationService.Translate(nameof(PlaceholderAdd));
public string ErrorCreateGame => translationService.Translate(nameof(ErrorCreateGame));
}
public static class ResourcesKey
@@ -78,4 +79,5 @@ public static class ResourcesKey
public static string RequestFailedStatusFormat => _instance?.RequestFailedStatusFormat ?? throw new InvalidOperationException("ResourcesKey.RequestFailedStatusFormat is not initialized.");
public static string ErrorFetchCategories => _instance?.ErrorFetchCategories ?? throw new InvalidOperationException("ResourcesKey.ErrorFetchCategories is not initialized.");
public static string PlaceholderAdd => _instance?.PlaceholderAdd ?? throw new InvalidOperationException("ResourcesKey.PlaceholderAdd is not initialized.");
public static string ErrorCreateGame => _instance?.ErrorCreateGame ?? throw new InvalidOperationException("ResourcesKey.ErrorCreateGame is not initialized.");
}

View File

@@ -2,5 +2,5 @@
public class GlobalConstants
{
public const string RegexSelectRow = @"[a-zA-Z_ \-]*";
public const string RegexName = @"[\w()\-_ ]*";
}

View File

@@ -30,5 +30,7 @@
"ErrorWhenFetchingData": "Erreur lors de la requête GET",
"RequestFailedStatusFormat": "Erreur lors de la réponse, code {0}",
"ErrorFetchCategories": "Erreur lors de la récupération des catégories",
"PlaceholderAdd": "Ajouter un nouveau"
"PlaceholderAdd": "Ajouter un nouveau",
"ErrorCreateGame": "Erreur lors de la Création d'un jeu"
}