Create game from form #15

Merged
Egamorf merged 8 commits from feature/create-game into main 2025-04-15 23:33:02 +02:00
14 changed files with 101 additions and 49 deletions
Showing only changes of commit 3447fa6eb1 - Show all commits

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.BlazorApp.Shared.Components.Slider
@using GameIdeas.Shared.Dto @using GameIdeas.Shared.Dto
<EditForm EditContext="EditContext" OnSubmit="HandleOnSubmit"> <EditForm EditContext="EditContext" OnSubmit="HandleOnSubmit">
<FluentValidationValidator/>
<div class="game-form"> <div class="game-form">
<div class="container"> <div class="container">
<div class="input-game"> <div class="input-game">
<div id="first-label" class="label">@ResourcesKey.Title :</div> <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>
<div class="input-game"> <div class="input-game">
<div class="label">@ResourcesKey.ReleaseDate :</div> <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>
<div class="input-game"> <div class="input-game">
<div class="label">@ResourcesKey.StorageSizeMo :</div> <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>
<div class="input-game"> <div class="input-game">
<div class="label">@ResourcesKey.Developers :</div> <div class="label">@ResourcesKey.Developers :</div>
@@ -61,14 +63,14 @@
{ {
<div class="input-game"> <div class="input-game">
<div class="label">@platform.Label :</div> <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>
</div> </div>
<div class="description-container"> <div class="description-container">
<div id="label-description">@ResourcesKey.Description :</div> <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>
<div class="buttons"> <div class="buttons">
<button type="reset" class="cancel" @onclick=HandleOnCancel> <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.Popup;
using GameIdeas.BlazorApp.Shared.Components.Select.Models; using GameIdeas.BlazorApp.Shared.Components.Select.Models;
using GameIdeas.BlazorApp.Shared.Components.Slider; using GameIdeas.BlazorApp.Shared.Components.Slider;
@@ -11,6 +12,7 @@ namespace GameIdeas.BlazorApp.Pages.Games.Components;
public partial class GameCreationForm public partial class GameCreationForm
{ {
[Inject] private IJSRuntime Js { get; set; } = default!; [Inject] private IJSRuntime Js { get; set; } = default!;
[Inject] private IGameGateway GameGateway { get; set; } = default!;
[CascadingParameter] private Popup? Popup { get; set; } [CascadingParameter] private Popup? Popup { get; set; }
[Parameter] public CategoriesDto? Categories { get; set; } [Parameter] public CategoriesDto? Categories { get; set; }
@@ -48,6 +50,6 @@ public partial class GameCreationForm
return; return;
} }
await GameGateway.CreateGame(GameDto);
} }
} }

View File

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

View File

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

View File

@@ -15,4 +15,4 @@
<BackdropFilter @ref="BackdropFilter" OnClick="HandleBackdropFilterClicked" CloseOnClick="@Closable" <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() private async Task HandleSubmitAdd()
{ {
if (Regex.IsMatch(AddLabel, GlobalConstants.RegexSelectRow) && if (Regex.IsMatch(AddLabel, GlobalConstants.RegexName) &&
Params.AddItem != null) Params.AddItem != null)
{ {
Values ??= []; Values ??= [];

View File

@@ -4,7 +4,7 @@ public static class Endpoints
{ {
public static class Game public static class Game
{ {
public static readonly string Create = "api/Game/Create";
} }
public static class Category 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%; height: 100%;
} }
.valid.modified:not([type=checkbox]) {
border: 2px solid var(--green);
}
.invalid { .invalid {
border: 2px solid var(--red) !important; 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 RequestFailedStatusFormat => translationService.Translate(nameof(RequestFailedStatusFormat));
public string ErrorFetchCategories => translationService.Translate(nameof(ErrorFetchCategories)); public string ErrorFetchCategories => translationService.Translate(nameof(ErrorFetchCategories));
public string PlaceholderAdd => translationService.Translate(nameof(PlaceholderAdd)); public string PlaceholderAdd => translationService.Translate(nameof(PlaceholderAdd));
public string ErrorCreateGame => translationService.Translate(nameof(ErrorCreateGame));
} }
public static class ResourcesKey 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 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 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 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 class GlobalConstants
{ {
public const string RegexSelectRow = @"[a-zA-Z_ \-]*"; public const string RegexName = @"[\w()\-_ ]*";
} }

View File

@@ -1,34 +1,36 @@
{ {
"GamesIdeas": "Game Ideas", "GamesIdeas": "Game Ideas",
"ManualAdd": "Manuel", "ManualAdd": "Manuel",
"AutoAdd": "Automatique", "AutoAdd": "Automatique",
"Login": "Se connecter", "Login": "Se connecter",
"Logout": "Se déconnecter", "Logout": "Se déconnecter",
"EnterUsername": "Nom d'utilisateur", "EnterUsername": "Nom d'utilisateur",
"EnterPassword": "Mot de passe", "EnterPassword": "Mot de passe",
"UserManager": "Gestion des utilisateurs", "UserManager": "Gestion des utilisateurs",
"Filters": "Les filtres", "Filters": "Les filtres",
"LastAdd": "Les ajouts récents", "LastAdd": "Les ajouts récents",
"Research": "Rechercher", "Research": "Rechercher",
"Platforms": "Plateformes", "Platforms": "Plateformes",
"Tags": "Genres", "Tags": "Genres",
"Publishers": "Editeurs", "Publishers": "Editeurs",
"Developers": "Développeurs", "Developers": "Développeurs",
"StorageSize": "Taille d'espace", "StorageSize": "Taille d'espace",
"StorageSizeMo": "Taille d'espace en Mo", "StorageSizeMo": "Taille d'espace en Mo",
"LastModification": "Dernière modifications", "LastModification": "Dernière modifications",
"ReleaseDate": "Date de parution", "ReleaseDate": "Date de parution",
"Title": "Titre", "Title": "Titre",
"Interest": "Intérêt", "Interest": "Intérêt",
"Properties": "Propriétés", "Properties": "Propriétés",
"Description": "Description", "Description": "Description",
"Save": "Enregister", "Save": "Enregister",
"Reset": "Annuler", "Reset": "Annuler",
"ErrorWhenPostingData": "Erreur lors de la requête POST", "ErrorWhenPostingData": "Erreur lors de la requête POST",
"ErrorWhenPutingData": "Erreur lors de la requête PUT", "ErrorWhenPutingData": "Erreur lors de la requête PUT",
"ErrorWhenDeletingData": "Erreur lors de la requête DELETE", "ErrorWhenDeletingData": "Erreur lors de la requête DELETE",
"ErrorWhenFetchingData": "Erreur lors de la requête GET", "ErrorWhenFetchingData": "Erreur lors de la requête GET",
"RequestFailedStatusFormat": "Erreur lors de la réponse, code {0}", "RequestFailedStatusFormat": "Erreur lors de la réponse, code {0}",
"ErrorFetchCategories": "Erreur lors de la récupération des catégories", "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"
} }