Fetch all categories for creation
All checks were successful
Game Ideas build for PR / build_blazor_app (pull_request) Successful in 55s

This commit is contained in:
2025-04-13 17:10:42 +02:00
parent 0fb5f8d682
commit 7e66e69582
35 changed files with 392 additions and 170 deletions

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>

View File

@@ -1,29 +1,32 @@
@using GameIdeas.BlazorApp.Shared.Components.Select @using GameIdeas.BlazorApp.Shared.Components.Select
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
@using GameIdeas.BlazorApp.Shared.Components.Slider @using GameIdeas.BlazorApp.Shared.Components.Slider
@using GameIdeas.Shared.Dto @using GameIdeas.Shared.Dto
<EditForm EditContext="EditContext"> <EditForm EditContext="EditContext" OnSubmit="HandleOnSubmit">
<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"> <input type="text" class="title" @bind=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"> <input type="date" class="date" @bind=GameDto.ReleaseDate>
</div> </div>
<div class="input-game"> <div class="input-game">
<div class="label">@ResourcesKey.StorageSize :</div> <div class="label">@ResourcesKey.StorageSizeMo :</div>
<input type="text" class="storage"> <input type="number" class="storage" @bind=GameDto.StorageSpace>
</div> </div>
<div class="input-game"> <div class="input-game">
<div class="label">@ResourcesKey.Developers :</div> <div class="label">@ResourcesKey.Developers :</div>
<MultipleSelectList TItem="DeveloperDto" Theme="SelectListTheme" /> <MultipleSelectList TItem="DeveloperDto" Theme="SelectListTheme" @bind-Values=GameDto.Developers
Items="CategoriesDto?.Developers?.Select(d => new SelectElement<DeveloperDto>(d, d.Name))" />
</div> </div>
<div class="input-game"> <div class="input-game">
<div class="label">@ResourcesKey.Publishers :</div> <div class="label">@ResourcesKey.Publishers :</div>
<MultipleSelectList TItem="PublisherDto" Theme="SelectListTheme" /> <MultipleSelectList TItem="PublisherDto" Theme="SelectListTheme" @bind-Values=GameDto.Publishers
Items="CategoriesDto?.Publishers?.Select(p => new SelectElement<PublisherDto>(p, p.Name))" />
</div> </div>
</div> </div>
<div class="container"> <div class="container">
@@ -35,15 +38,19 @@
</div> </div>
<div class="input-game"> <div class="input-game">
<div class="label">@ResourcesKey.Properties :</div> <div class="label">@ResourcesKey.Properties :</div>
<MultipleSelectList TItem="PropertyDto" Theme="SelectListTheme" /> <MultipleSelectList TItem="PropertyDto" Theme="SelectListTheme" @bind-Values=GameDto.Properties
Items="CategoriesDto?.Properties?.Select(p => new SelectElement<PropertyDto>(p, p.Label))" />
</div> </div>
<div class="input-game"> <div class="input-game">
<div class="label">@ResourcesKey.Genres :</div> <div class="label">@ResourcesKey.Genres :</div>
<MultipleSelectList TItem="TagDto" Theme="SelectListTheme" /> <MultipleSelectList TItem="TagDto" Theme="SelectListTheme" @bind-Values=GameDto.Tags
Items="CategoriesDto?.Tags?.Select(t => new SelectElement<TagDto>(t, t.Label))" />
</div> </div>
<div class="input-game"> <div class="input-game">
<div class="label">@ResourcesKey.Platforms :</div> <div class="label">@ResourcesKey.Platforms :</div>
<MultipleSelectList TItem="PlatformDto" Theme="SelectListTheme" /> <MultipleSelectList TItem="PlatformDto" Theme="SelectListTheme" @bind-Values=GameDto.Platforms
Items="CategoriesDto?.Platforms?.Select(p => new SelectElement<PlatformDto>(p, p.Label))" />
</div> </div>
</div> </div>
</div> </div>
@@ -51,4 +58,12 @@
<div id="label-description">@ResourcesKey.Description :</div> <div id="label-description">@ResourcesKey.Description :</div>
<input type="text" class="description" @bind-value=GameDto.Description> <input type="text" class="description" @bind-value=GameDto.Description>
</div> </div>
<div class="buttons">
<button type="reset" class="cancel" @onclick=HandleOnCancel>
@ResourcesKey.Reset
</button>
<button type="submit" class="submit">
@ResourcesKey.Save
</button>
</div>
</EditForm> </EditForm>

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,26 +12,45 @@ 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; }
private GameDto GameDto = new(); private GameDto GameDto = new();
private CategoriesDto CategoriesDto = new();
private EditContext? EditContext; private EditContext? EditContext;
private readonly SelectListTheme SelectListTheme = SelectListTheme.Creation; private readonly SelectListTheme SelectListTheme = SelectListTheme.Creation;
private readonly SliderParams SliderParams = new() { Gap = 1, Min = 1, Max = 5 }; private readonly SliderParams SliderParams = new() { Gap = 1, Min = 1, Max = 5 };
protected override void OnInitialized() protected override async Task OnInitializedAsync()
{ {
EditContext = new(GameDto); EditContext = new(GameDto);
CategoriesDto = await GameGateway.FetchCategories();
if (Popup != null) if (Popup != null)
{ {
Popup.StateChanged += async (_, isOpen) => await HandlePopupStateChanged(); Popup.StateChanged += async (_, isOpen) => await HandlePopupStateChanged();
} }
base.OnInitialized(); await base.OnInitializedAsync();
} }
private async Task HandlePopupStateChanged() private async Task HandlePopupStateChanged()
{ {
await Js.InvokeVoidAsync("resizeGameForm"); await Js.InvokeVoidAsync("resizeGameForm");
} }
private void HandleOnCancel()
{
Popup?.Close();
}
private async Task HandleOnSubmit(EditContext args)
{
if (EditContext?.Validate() == false)
{
return;
}
}
} }

View File

@@ -62,3 +62,7 @@ input {
padding: 0 20px; padding: 0 20px;
align-content: center; align-content: center;
} }
input[type="number"]::-webkit-inner-spin-button {
-webkit-appearance: none;
}

View File

@@ -5,13 +5,11 @@
<div class="duplicate"> <div class="duplicate">
<MultipleSelectList TItem="string" <MultipleSelectList TItem="string"
Items="Plateforms"
@bind-Values=GameFilterParams!.Platforms @bind-Values=GameFilterParams!.Platforms
Placeholder="@ResourcesKey.Platforms" Placeholder="@ResourcesKey.Platforms"
Theme="SelectListTheme.AdvancedFilter" /> Theme="SelectListTheme.AdvancedFilter" />
<MultipleSelectList TItem="string" <MultipleSelectList TItem="string"
Items="Genres"
Placeholder="@ResourcesKey.Genres" Placeholder="@ResourcesKey.Genres"
@bind-Values=GameFilterParams!.Tags @bind-Values=GameFilterParams!.Tags
Theme="SelectListTheme.AdvancedFilter" /> Theme="SelectListTheme.AdvancedFilter" />
@@ -19,31 +17,26 @@
<MultipleSelectList TItem="string" <MultipleSelectList TItem="string"
Items="Publishers"
Placeholder="@ResourcesKey.Publishers" Placeholder="@ResourcesKey.Publishers"
@bind-Values=GameFilterParams!.Publishers @bind-Values=GameFilterParams!.Publishers
Theme="SelectListTheme.AdvancedFilter" /> Theme="SelectListTheme.AdvancedFilter" />
<MultipleSelectList TItem="string" <MultipleSelectList TItem="string"
Items="Developers"
Placeholder="@ResourcesKey.Developers" Placeholder="@ResourcesKey.Developers"
@bind-Values=GameFilterParams!.Developers @bind-Values=GameFilterParams!.Developers
Theme="SelectListTheme.AdvancedFilter" /> Theme="SelectListTheme.AdvancedFilter" />
<MultipleSelectList TItem="string" <MultipleSelectList TItem="string"
Items="StorageSizes"
Placeholder="@ResourcesKey.StorageSize" Placeholder="@ResourcesKey.StorageSize"
@bind-Values=GameFilterParams!.StorageSizes @bind-Values=GameFilterParams!.StorageSizes
Theme="SelectListTheme.AdvancedFilter" /> Theme="SelectListTheme.AdvancedFilter" />
<MultipleSelectList TItem="string" <MultipleSelectList TItem="string"
Items="LastModifiedDates"
Placeholder="@ResourcesKey.LastModification" Placeholder="@ResourcesKey.LastModification"
@bind-Values=GameFilterParams!.LastModification @bind-Values=GameFilterParams!.LastModification
Theme="SelectListTheme.AdvancedFilter" /> Theme="SelectListTheme.AdvancedFilter" />
<MultipleSelectList TItem="string" <MultipleSelectList TItem="string"
Items="ReleaseDates"
Placeholder="@ResourcesKey.ReleaseDate" Placeholder="@ResourcesKey.ReleaseDate"
@bind-Values=GameFilterParams!.ReleaseDates @bind-Values=GameFilterParams!.ReleaseDates
Theme="SelectListTheme.AdvancedFilter" /> Theme="SelectListTheme.AdvancedFilter" />

View File

@@ -7,56 +7,4 @@ public partial class AdvancedGameFilter
{ {
[Parameter] public GameFilterParams? GameFilterParams { get; set; } [Parameter] public GameFilterParams? GameFilterParams { get; set; }
[Parameter] public EventCallback<GameFilterParams> GameFilterParamsChanged { get; set; } [Parameter] public EventCallback<GameFilterParams> GameFilterParamsChanged { get; set; }
private readonly IEnumerable<SelectElement<string>> Plateforms = [
new() { Item = "Steam", Label = "Steam" },
new() { Item = "GOG", Label = "GOG" },
new() { Item = "Epic games", Label = "Epic games" },
new() { Item = "Ubisoft", Label = "Ubisoft" },
];
private readonly IEnumerable<SelectElement<string>> Genres = [
new() { Item = "Rogue Like", Label = "Rogue Like" },
new() { Item = "Aventure", Label = "Aventure" },
new() { Item = "RPG", Label = "RPG" },
new() { Item = "Fast FPS", Label = "Fast FPS" },
];
private readonly IEnumerable<SelectElement<string>> Publishers = [
new() { Item = "Electronic Arts", Label = "Electronic Arts" },
new() { Item = "Ubisoft", Label = "Ubisoft" },
new() { Item = "Activision Blizzard", Label = "Activision Blizzard" },
new() { Item = "Bethesda", Label = "Bethesda" }
];
private readonly IEnumerable<SelectElement<string>> Developers = [
new() { Item = "CD Projekt Red", Label = "CD Projekt Red" },
new() { Item = "Naughty Dog", Label = "Naughty Dog" },
new() { Item = "Rockstar Games", Label = "Rockstar Games" },
new() { Item = "FromSoftware", Label = "FromSoftware" },
];
private readonly IEnumerable<SelectElement<string>> StorageSizes = [
new() { Item = "1 Go", Label = "1 Go" },
new() { Item = "10 Go", Label = "10 Go" },
new() { Item = "50 Go", Label = "50 Go" },
new() { Item = "100 Go", Label = "100 Go" },
];
private readonly IEnumerable<SelectElement<string>> LastModifiedDates = [
new() { Item = "2023-12-15", Label = "15 D<>cembre 2023" },
new() { Item = "2024-01-20", Label = "20 Janvier 2024" },
new() { Item = "2024-02-05", Label = "5 F<>vrier 2024" },
new() { Item = "2024-03-10", Label = "10 Mars 2024" },
];
private readonly IEnumerable<SelectElement<string>> ReleaseDates = [
new() { Item = "2023-11-11", Label = "11 Novembre 2023" },
new() { Item = "2024-01-25", Label = "25 Janvier 2024" },
new() { Item = "2024-03-03", Label = "3 Mars 2024" },
new() { Item = "2024-04-15", Label = "15 Avril 2024" },
];
} }

View File

@@ -4,13 +4,13 @@
@using GameIdeas.BlazorApp.Shared.Components.SliderRange @using GameIdeas.BlazorApp.Shared.Components.SliderRange
@using GameIdeas.BlazorApp.Shared.Models @using GameIdeas.BlazorApp.Shared.Models
@using GameIdeas.Shared.Dto @using GameIdeas.Shared.Dto
@using GameIdeas.Shared.Enum
<div class="form-filter"> <div class="form-filter">
<SelectList TItem="Func<GameDto, object>" <SelectList TItem="Func<GameDto, object>" Items="GameProperties"
Headers="SortTypes" @bind-Value=GameFilterParams.SortProperty
Items="GameProperties" THeader="SortType" Headers="SortTypes"
@bind-Value=GameFilterParams!.SortProperty @bind-Header=GameFilterParams.SortType
HeaderChanged=HandleSortTypeChanged
Theme="SelectListTheme.Sort"> Theme="SelectListTheme.Sort">
<div class="square-button"> <div class="square-button">
<svg class="sort-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="sort-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
@@ -38,7 +38,6 @@
<div class="select-container"> <div class="select-container">
<MultipleSelectList TItem="string" <MultipleSelectList TItem="string"
Items="Plateforms"
Placeholder="@ResourcesKey.Platforms" Placeholder="@ResourcesKey.Platforms"
@bind-Values=GameFilterParams.Platforms @bind-Values=GameFilterParams.Platforms
Theme="SelectListTheme.Filter" /> Theme="SelectListTheme.Filter" />
@@ -46,7 +45,6 @@
<div class="select-container"> <div class="select-container">
<MultipleSelectList TItem="string" <MultipleSelectList TItem="string"
Items="Genres"
Placeholder="@ResourcesKey.Genres" Placeholder="@ResourcesKey.Genres"
@bind-Values=GameFilterParams.Tags @bind-Values=GameFilterParams.Tags
Theme="SelectListTheme.Filter" /> Theme="SelectListTheme.Filter" />

View File

@@ -15,28 +15,14 @@ public partial class GameFilter
[Parameter] public DisplayType DisplayType { get; set; } [Parameter] public DisplayType DisplayType { get; set; }
[Parameter] public EventCallback<DisplayType> DisplayTypeChanged { get; set; } [Parameter] public EventCallback<DisplayType> DisplayTypeChanged { get; set; }
private readonly IEnumerable<SelectElement<Func<GameDto?, object?>>> SortTypes = [ private readonly IEnumerable<SelectElement<SortType>> SortTypes = [
new() { Item = _ => SortType.Ascending, Label = "Ascendant", IsSelected = true }, new(SortType.Ascending, "Ascendant") { IsSelected = true },
new() { Item = _ => SortType.Descending, Label = "Descendant" } new(SortType.Descending, "Descendant")
]; ];
private readonly IEnumerable<SelectElement<Func<GameDto?, object?>>> GameProperties = [ private readonly IEnumerable<SelectElement<Func<GameDto?, object?>>> GameProperties = [
new() { Item = game => game?.Title, Label = "Nom", IsSelected = true }, new(game => game?.Title, "Nom") { IsSelected = true },
new() { Item = game => game?.ReleaseDate, Label = "Date de parution" } new(game => game?.ReleaseDate, "Date de parution"),
];
private readonly IEnumerable<SelectElement<string>> Plateforms = [
new() { Item = "Steam", Label = "Steam" },
new() { Item = "GOG", Label = "GOG" },
new() { Item = "Epic games", Label = "Epic games" },
new() { Item = "Ubisoft", Label = "Ubisoft" },
];
private readonly IEnumerable<SelectElement<string>> Genres = [
new() { Item = "Rogue Like", Label = "Rogue Like" },
new() { Item = "Aventure", Label = "Aventure" },
new() { Item = "RPG", Label = "RPG" },
new() { Item = "Fast FPS", Label = "Fast FPS" },
]; ];
private EditContext? EditContext; private EditContext? EditContext;
@@ -52,11 +38,6 @@ public partial class GameFilter
}; };
} }
private void HandleSortTypeChanged(Func<GameDto?, object?> getHeader)
{
GameFilterParams.SortType = (SortType?)getHeader(null) ?? SortType.Ascending;
}
private async Task HandleDisplayClicked(DisplayType displayType) private async Task HandleDisplayClicked(DisplayType displayType)
{ {
DisplayType = displayType; DisplayType = displayType;

View File

@@ -5,7 +5,7 @@ namespace GameIdeas.BlazorApp.Pages.Games.Filter;
public class GameFilterParams public class GameFilterParams
{ {
public SortType? SortType { get; set; } public SortType SortType { get; set; } = SortType.Ascending;
public Func<GameDto?, object?>? SortProperty { get; set; } public Func<GameDto?, object?>? SortProperty { get; set; }
public string? SearchName { get; set; } public string? SearchName { get; set; }
public IEnumerable<string>? Platforms { get; set; } public IEnumerable<string>? Platforms { get; set; }

View File

@@ -0,0 +1,24 @@
using GameIdeas.BlazorApp.Services;
using GameIdeas.BlazorApp.Shared.Constants;
using GameIdeas.BlazorApp.Shared.Exceptions;
using GameIdeas.Resources;
using GameIdeas.Shared.Dto;
namespace GameIdeas.BlazorApp.Pages.Games.Gateways;
public class GameGateway(IHttpClientService httpClientService) : IGameGateway
{
public async Task<CategoriesDto> FetchCategories()
{
try
{
var result = await httpClientService.FetchDataAsync<CategoriesDto>(Endpoints.Category.AllCategories);
return result ?? throw new InvalidOperationException(ResourcesKey.ErrorFetchCategories);
}
catch (Exception)
{
throw new CategoryNotFoundException(ResourcesKey.ErrorFetchCategories);
}
}
}

View File

@@ -0,0 +1,8 @@
using GameIdeas.Shared.Dto;
namespace GameIdeas.BlazorApp.Pages.Games.Gateways;
public interface IGameGateway
{
Task<CategoriesDto> FetchCategories();
}

View File

@@ -23,7 +23,7 @@
</svg> </svg>
</div> </div>
<SelectList @ref="SelectListAdd" TItem="AddType" Items="SelectElements" <SelectList @ref="SelectListAdd" TItem="AddType" Items="SelectElements"
ValueChanged=HandleAddTypeClickedAsync ValueChanged=HandleAddTypeClickedAsync THeader="object"
Theme="SelectListTheme.Navigation" AlignRight=true> Theme="SelectListTheme.Navigation" AlignRight=true>
<div class="second-button button"> <div class="second-button button">
<svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> <svg class="button-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">

View File

@@ -14,12 +14,12 @@ public partial class GameHeader : ComponentBase
private readonly IEnumerable<SelectElement<AddType>> SelectElements = [ private readonly IEnumerable<SelectElement<AddType>> SelectElements = [
new SelectElement<AddType> { Item = AddType.Manual, Label = ResourcesKey.ManualAdd }, new SelectElement<AddType>(AddType.Manual, ResourcesKey.ManualAdd),
new SelectElement<AddType> { Item = AddType.Auto, Label = ResourcesKey.AutoAdd } new SelectElement<AddType> (AddType.Auto, ResourcesKey.AutoAdd)
]; ];
private AccountSettings? AccountSettings; private AccountSettings? AccountSettings;
private SelectList<AddType>? SelectListAdd; private SelectList<AddType, object>? SelectListAdd;
private void HandleIconClicked() private void HandleIconClicked()
{ {

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.Pages.Games.Gateways;
using GameIdeas.BlazorApp.Services; using GameIdeas.BlazorApp.Services;
using GameIdeas.Resources; using GameIdeas.Resources;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
@@ -22,7 +23,8 @@ builder.Services.AddHttpClient(
client.Timeout = TimeSpan.FromMinutes(3); client.Timeout = TimeSpan.FromMinutes(3);
}); });
builder.Services.AddScoped<AuthentificationService>(); builder.Services.AddScoped<IHttpClientService, HttpClientService>();
builder.Services.AddScoped<IGameGateway, GameGateway>();
builder.Services.AddSingleton<TranslationService>(); builder.Services.AddSingleton<TranslationService>();
builder.Services.AddSingleton<Translations>(); builder.Services.AddSingleton<Translations>();

View File

@@ -1,21 +0,0 @@
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

@@ -0,0 +1,125 @@
using GameIdeas.Resources;
using System.Net.Http.Headers;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Text;
namespace GameIdeas.BlazorApp.Services;
public class HttpClientService(IHttpClientFactory httpClientFactory, ILoggerFactory loggerFactory) : IHttpClientService
{
private readonly HttpClient httpClient = httpClientFactory.CreateClient("GameIdeas.WebAPI");
private readonly ILogger<HttpClientService> logger = loggerFactory.CreateLogger<HttpClientService>();
private readonly JsonSerializerOptions _optionsCamelCase = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
};
private readonly JsonSerializerOptions _optionsCaseInsensitive = new()
{
PropertyNameCaseInsensitive = true,
ReferenceHandler = ReferenceHandler.Preserve
};
public async Task<T?> PostAsync<T>(string url, object data)
{
var jsonContent = JsonSerializer.Serialize(data, _optionsCamelCase);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(url, content);
return await GetResultValue<T>(response, ResourcesKey.ErrorWhenPostingData);
}
public async Task<T?> PutAsync<T>(string url, object data)
{
var jsonContent = JsonSerializer.Serialize(data, _optionsCamelCase);
var content = new StringContent(jsonContent, Encoding.UTF8, "application/json");
var response = await httpClient.PutAsync(url, content);
return await GetResultValue<T>(response, ResourcesKey.ErrorWhenPutingData);
}
public async Task<T?> DeleteAsync<T>(string? url)
{
var response = await httpClient.DeleteAsync(url);
return await GetResultValue<T>(response, ResourcesKey.ErrorWhenDeletingData);
}
public async Task<T?> FetchDataAsync<T>(string? url)
{
var response = await httpClient.GetAsync(url);
return await GetResultValue<T>(response, ResourcesKey.ErrorWhenFetchingData);
}
public async Task<byte[]?> FetchBytesAsync(string? url)
{
var response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsByteArrayAsync();
}
throw new HttpRequestException(
$"{ResourcesKey.ErrorWhenFetchingData} + StatusCode: {response.StatusCode} " +
$"+ Reason: {response.ReasonPhrase}");
}
public async Task<Stream?> FetchStreamAsync(string? url)
{
var response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStreamAsync();
}
throw new HttpRequestException($"{ResourcesKey.ErrorWhenFetchingData} + StatusCode: {response.StatusCode} " +
$"+ Reason: {response.ReasonPhrase}");
}
public async Task<T?> PostFileAsync<T>(string? url, Stream fileStream, string fileName, string contentType)
{
using var content = new MultipartFormDataContent();
var streamContent = new StreamContent(fileStream);
streamContent.Headers.ContentType = new MediaTypeHeaderValue(contentType);
content.Add(streamContent, "file", fileName);
var response = await httpClient.PostAsync(url, content);
return await GetResultValue<T>(response, ResourcesKey.ErrorWhenPostingData);
}
private async Task<T?> GetResultValue<T>(HttpResponseMessage response, string errorMessage)
{
if (response.IsSuccessStatusCode)
{
try
{
var responseContent = await response.Content.ReadAsStringAsync();
if (string.IsNullOrWhiteSpace(responseContent))
{
return default;
}
var result = JsonSerializer.Deserialize<T>(responseContent, _optionsCaseInsensitive);
return result;
}
catch (Exception ex)
{
throw new JsonException(ex.Message);
}
}
logger.LogError(ResourcesKey.RequestFailedStatusFormat, response.StatusCode);
throw new HttpRequestException(
$"{errorMessage} + StatusCode: {response.StatusCode} + Reason: {response.ReasonPhrase}");
}
}

View File

@@ -0,0 +1,12 @@
namespace GameIdeas.BlazorApp.Services;
public interface IHttpClientService
{
Task<T?> PostAsync<T>(string url, object data);
Task<T?> PutAsync<T>(string url, object data);
Task<T?> DeleteAsync<T>(string? url);
Task<T?> FetchDataAsync<T>(string? url);
Task<byte[]?> FetchBytesAsync(string? url);
Task<Stream?> FetchStreamAsync(string? url);
Task<T?> PostFileAsync<T>(string? url, Stream fileStream, string fileName, string contentType);
}

View File

@@ -3,7 +3,7 @@
<div class="account-setting-container" tabindex="1000"> <div class="account-setting-container" tabindex="1000">
<div class="account-setting-content @(ContentVisile ? string.Empty : "invisible")"> <div class="account-setting-content @(ContentVisile ? string.Empty : "invisible")">
@if (!AuthentificationService.IsLogin) @if (!IsLogin)
{ {
<EditForm EditContext="EditContext" OnSubmit="HandleLoginSubmit"> <EditForm EditContext="EditContext" OnSubmit="HandleLoginSubmit">
<FluentValidationValidator /> <FluentValidationValidator />

View File

@@ -1,28 +1,21 @@
using GameIdeas.BlazorApp.Services;
using GameIdeas.Shared.Dto; using GameIdeas.Shared.Dto;
using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Forms;
namespace GameIdeas.BlazorApp.Shared.Components.Account; namespace GameIdeas.BlazorApp.Shared.Components.Account;
public partial class AccountSettings ( public partial class AccountSettings
AuthentificationService AuthentificationService)
{ {
private bool ContentVisile = false; private bool ContentVisile = false;
private EditContext? EditContext; private EditContext? EditContext;
private LoginDto LoginDto = new(); private LoginDto LoginDto = new();
private bool IsLoading = false; private bool IsLoading = false;
private bool IsLogin = true;
protected override void OnInitialized() protected override void OnInitialized()
{ {
EditContext = new EditContext(LoginDto); EditContext = new EditContext(LoginDto);
} }
public void Open()
{
ContentVisile = true;
StateHasChanged();
}
public void Close() public void Close()
{ {
ContentVisile = false; ContentVisile = false;
@@ -45,15 +38,10 @@ public partial class AccountSettings (
IsLoading = true; IsLoading = true;
await Task.Delay(TimeSpan.FromSeconds(5)); await Task.Delay(TimeSpan.FromSeconds(5));
Close(); Close();
AuthentificationService.Login();
IsLoading = false; IsLoading = false;
LoginDto = new();
EditContext = new EditContext(LoginDto);
} }
private void HandleLogoutClicked() private void HandleLogoutClicked()
{ {
Close(); Close();
AuthentificationService.Logout();
} }
} }

View File

@@ -1,9 +1,9 @@
namespace GameIdeas.BlazorApp.Shared.Components.Select.Models; namespace GameIdeas.BlazorApp.Shared.Components.Select.Models;
public class SelectElement<TItem> public class SelectElement<TItem>(TItem item, string? label)
{ {
public TItem? Item { get; set; } public TItem Item { get; set; } = item;
public string? Label { get; set; } public string? Label { get; set; } = label;
public bool IsSelected { get; set; } = false; public bool IsSelected { get; set; } = false;
public bool IsNew { get; set; } = false; public bool IsNew { get; set; } = false;
} }

View File

@@ -1,6 +1,7 @@
@using GameIdeas.BlazorApp.Shared.Components.BackdropFilter @using GameIdeas.BlazorApp.Shared.Components.BackdropFilter
@using GameIdeas.BlazorApp.Shared.Components.Select.Components @using GameIdeas.BlazorApp.Shared.Components.Select.Components
@typeparam TItem @typeparam TItem
@typeparam THeader
<div class="select-list"> <div class="select-list">
<div class="select-button" @onclick=HandleButtonClicked> <div class="select-button" @onclick=HandleButtonClicked>
@@ -11,10 +12,10 @@
@if (IsContentOpen) @if (IsContentOpen)
{ {
<div class="select-content @(Enum.GetName(Theme)?.ToLower())"> <div class="select-content @(Enum.GetName(Theme)?.ToLower())">
@foreach (var item in Headers) @foreach (var header in Headers)
{ {
<SelectListElement TItem="TItem" <SelectListElement TItem="THeader"
Value="item" Value="header"
ValueChanged="HandleHeaderClicked" ValueChanged="HandleHeaderClicked"
Theme="Theme" /> Theme="Theme" />
} }

View File

@@ -3,15 +3,15 @@ using Microsoft.AspNetCore.Components;
namespace GameIdeas.BlazorApp.Shared.Components.Select; namespace GameIdeas.BlazorApp.Shared.Components.Select;
public partial class SelectList<TItem> public partial class SelectList<TItem, THeader>
{ {
[Parameter] public RenderFragment? ChildContent { get; set; } [Parameter] public RenderFragment? ChildContent { get; set; }
[Parameter] public TItem? Value { get; set; } [Parameter] public TItem? Value { get; set; }
[Parameter] public EventCallback<TItem?> ValueChanged { get; set; } [Parameter] public EventCallback<TItem?> ValueChanged { get; set; }
[Parameter] public TItem? Header { get; set; } [Parameter] public THeader? Header { get; set; }
[Parameter] public EventCallback<TItem?> HeaderChanged { get; set; } [Parameter] public EventCallback<THeader?> HeaderChanged { get; set; }
[Parameter] public IEnumerable<SelectElement<TItem>> Items { get; set; } = []; [Parameter] public IEnumerable<SelectElement<TItem>> Items { get; set; } = [];
[Parameter] public IEnumerable<SelectElement<TItem>> Headers { get; set; } = []; [Parameter] public IEnumerable<SelectElement<THeader>> Headers { get; set; } = [];
[Parameter] public SelectListTheme Theme { get; set; } [Parameter] public SelectListTheme Theme { get; set; }
[Parameter] public bool AlignRight { get; set; } [Parameter] public bool AlignRight { get; set; }
@@ -41,7 +41,7 @@ public partial class SelectList<TItem>
await ValueChanged.InvokeAsync(Value); await ValueChanged.InvokeAsync(Value);
} }
private async Task HandleHeaderClicked(SelectElement<TItem> selectedValue) private async Task HandleHeaderClicked(SelectElement<THeader> selectedValue)
{ {
foreach (var header in Headers) foreach (var header in Headers)
{ {

View File

@@ -0,0 +1,14 @@
namespace GameIdeas.BlazorApp.Shared.Constants;
public static class Endpoints
{
public static class Game
{
}
public static class Category
{
public static readonly string AllCategories = "api/Category/All";
}
}

View File

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

View File

@@ -19,12 +19,21 @@ public class Translations (TranslationService translationService)
public string Publishers => translationService.Translate(nameof(Publishers)); public string Publishers => translationService.Translate(nameof(Publishers));
public string Developers => translationService.Translate(nameof(Developers)); public string Developers => translationService.Translate(nameof(Developers));
public string StorageSize => translationService.Translate(nameof(StorageSize)); public string StorageSize => translationService.Translate(nameof(StorageSize));
public string StorageSizeMo => translationService.Translate(nameof(StorageSizeMo));
public string LastModification => translationService.Translate(nameof(LastModification)); public string LastModification => translationService.Translate(nameof(LastModification));
public string ReleaseDate => translationService.Translate(nameof(ReleaseDate)); public string ReleaseDate => translationService.Translate(nameof(ReleaseDate));
public string Title => translationService.Translate(nameof(Title)); public string Title => translationService.Translate(nameof(Title));
public string Interest => translationService.Translate(nameof(Interest)); public string Interest => translationService.Translate(nameof(Interest));
public string Properties => translationService.Translate(nameof(Properties)); public string Properties => translationService.Translate(nameof(Properties));
public string Description => translationService.Translate(nameof(Description)); public string Description => translationService.Translate(nameof(Description));
public string Save => translationService.Translate(nameof(Save));
public string Reset => translationService.Translate(nameof(Reset));
public string ErrorWhenPostingData => translationService.Translate(nameof(ErrorWhenPostingData));
public string ErrorWhenPutingData => translationService.Translate(nameof(ErrorWhenPutingData));
public string ErrorWhenDeletingData => translationService.Translate(nameof(ErrorWhenDeletingData));
public string ErrorWhenFetchingData => translationService.Translate(nameof(ErrorWhenFetchingData));
public string RequestFailedStatusFormat => translationService.Translate(nameof(RequestFailedStatusFormat));
public string ErrorFetchCategories => translationService.Translate(nameof(ErrorFetchCategories));
} }
public static class ResourcesKey public static class ResourcesKey
@@ -52,10 +61,19 @@ public static class ResourcesKey
public static string Publishers => _instance?.Publishers ?? throw new InvalidOperationException("ResourcesKey.Publishers is not initialized."); public static string Publishers => _instance?.Publishers ?? throw new InvalidOperationException("ResourcesKey.Publishers is not initialized.");
public static string Developers => _instance?.Developers ?? throw new InvalidOperationException("ResourcesKey.Developers is not initialized."); public static string Developers => _instance?.Developers ?? throw new InvalidOperationException("ResourcesKey.Developers is not initialized.");
public static string StorageSize => _instance?.StorageSize ?? throw new InvalidOperationException("ResourcesKey.StorageSize is not initialized."); public static string StorageSize => _instance?.StorageSize ?? throw new InvalidOperationException("ResourcesKey.StorageSize is not initialized.");
public static string StorageSizeMo => _instance?.StorageSizeMo ?? throw new InvalidOperationException("ResourcesKey.StorageSizeMo is not initialized.");
public static string LastModification => _instance?.LastModification ?? throw new InvalidOperationException("ResourcesKey.LastModification is not initialized."); public static string LastModification => _instance?.LastModification ?? throw new InvalidOperationException("ResourcesKey.LastModification is not initialized.");
public static string ReleaseDate => _instance?.ReleaseDate ?? throw new InvalidOperationException("ResourcesKey.ReleaseDate is not initialized."); public static string ReleaseDate => _instance?.ReleaseDate ?? throw new InvalidOperationException("ResourcesKey.ReleaseDate is not initialized.");
public static string Title => _instance?.Title ?? throw new InvalidOperationException("ResourcesKey.Title is not initialized."); public static string Title => _instance?.Title ?? throw new InvalidOperationException("ResourcesKey.Title is not initialized.");
public static string Interest => _instance?.Interest ?? throw new InvalidOperationException("ResourcesKey.Interest is not initialized."); public static string Interest => _instance?.Interest ?? throw new InvalidOperationException("ResourcesKey.Interest is not initialized.");
public static string Properties => _instance?.Properties ?? throw new InvalidOperationException("ResourcesKey.Properties is not initialized."); public static string Properties => _instance?.Properties ?? throw new InvalidOperationException("ResourcesKey.Properties is not initialized.");
public static string Description => _instance?.Description ?? throw new InvalidOperationException("ResourcesKey.Description is not initialized."); public static string Description => _instance?.Description ?? throw new InvalidOperationException("ResourcesKey.Description is not initialized.");
public static string Save => _instance?.Save ?? throw new InvalidOperationException("ResourcesKey.Save is not initialized.");
public static string Reset => _instance?.Reset ?? throw new InvalidOperationException("ResourcesKey.Reset is not initialized.");
public static string ErrorWhenPostingData => _instance?.ErrorWhenPostingData ?? throw new InvalidOperationException("ResourcesKey.ErrorWhenPostingData is not initialized.");
public static string ErrorWhenPutingData => _instance?.ErrorWhenPutingData ?? throw new InvalidOperationException("ResourcesKey.ErrorWhenPutingData is not initialized.");
public static string ErrorWhenDeletingData => _instance?.ErrorWhenDeletingData ?? throw new InvalidOperationException("ResourcesKey.ErrorWhenDeletingData is not initialized.");
public static string ErrorWhenFetchingData => _instance?.ErrorWhenFetchingData ?? throw new InvalidOperationException("ResourcesKey.ErrorWhenFetchingData 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.");
} }

View File

@@ -0,0 +1,10 @@
namespace GameIdeas.Shared.Dto;
public class CategoriesDto
{
public IEnumerable<PlatformDto>? Platforms { get; set; }
public IEnumerable<PropertyDto>? Properties { get; set; }
public IEnumerable<TagDto>? Tags { get; set; }
public IEnumerable<DeveloperDto>? Developers { get; set; }
public IEnumerable<PublisherDto>? Publishers { get; set; }
}

View File

@@ -0,0 +1,26 @@
using GameIdeas.Shared.Dto;
using GameIdeas.WebAPI.Services.Interfaces;
using Microsoft.AspNetCore.Mvc;
namespace GameIdeas.WebAPI.Controllers;
[ApiController]
[Route("api/[controller]")]
public class CategoryController(ICategoryService categoryService, ILoggerFactory loggerFactory) : Controller
{
private readonly ILogger<CategoryController> logger = loggerFactory.CreateLogger<CategoryController>();
[HttpGet("All")]
public async Task<ActionResult<CategoriesDto>> FetchAllCategories()
{
try
{
return Ok(await categoryService.GetCategories());
}
catch (Exception e)
{
logger.LogError(e, "Internal error while fetching categories");
return StatusCode(500, e.Message);
}
}
}

View File

@@ -1,5 +1,5 @@
using GameIdeas.Shared.Dto; using GameIdeas.Shared.Dto;
using GameIdeas.WebAPI.Services; using GameIdeas.WebAPI.Services.Interfaces;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
namespace GameIdeas.WebAPI.Controllers; namespace GameIdeas.WebAPI.Controllers;
@@ -7,7 +7,7 @@ namespace GameIdeas.WebAPI.Controllers;
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
public class GameController(GameService gameService, ILoggerFactory loggerFactory) : Controller public class GameController(IGameService gameService, ILoggerFactory loggerFactory) : Controller
{ {
private readonly ILogger<GameController> logger = loggerFactory.CreateLogger<GameController>(); private readonly ILogger<GameController> logger = loggerFactory.CreateLogger<GameController>();

View File

@@ -15,10 +15,19 @@
"Publishers": "Editeurs", "Publishers": "Editeurs",
"Developers": "Développeurs", "Developers": "Développeurs",
"StorageSize": "Taille d'espace", "StorageSize": "Taille d'espace",
"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",
"Reset": "Annuler",
"ErrorWhenPostingData": "Erreur lors de la requête POST",
"ErrorWhenPutingData": "Erreur lors de la requête PUT",
"ErrorWhenDeletingData": "Erreur lors de la requête DELETE",
"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"
} }

View File

@@ -2,6 +2,7 @@ using GameIdeas.Resources;
using GameIdeas.WebAPI.Context; using GameIdeas.WebAPI.Context;
using GameIdeas.WebAPI.Profiles; using GameIdeas.WebAPI.Profiles;
using GameIdeas.WebAPI.Services; using GameIdeas.WebAPI.Services;
using GameIdeas.WebAPI.Services.Interfaces;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@@ -31,11 +32,10 @@ services.AddDbContext<GameIdeasContext>(dbContextOptions);
services.AddSingleton<TranslationService>(); services.AddSingleton<TranslationService>();
services.AddSingleton<Translations>(); services.AddSingleton<Translations>();
services.AddScoped<GameService>(); services.AddScoped<IGameService, GameService>();
services.AddScoped<ICategoryService, CategoryService>();
services.AddAutoMapper(typeof(GameProfile).Assembly); services.AddAutoMapper(AppDomain.CurrentDomain.GetAssemblies());
services.AddAutoMapper(typeof(UserProfile).Assembly);
services.AddAutoMapper(typeof(CategoryProfile).Assembly);
services.AddControllers(); services.AddControllers();

View File

@@ -0,0 +1,28 @@
using AutoMapper;
using GameIdeas.Shared.Dto;
using GameIdeas.WebAPI.Context;
using GameIdeas.WebAPI.Services.Interfaces;
using Microsoft.EntityFrameworkCore;
namespace GameIdeas.WebAPI.Services;
public class CategoryService(GameIdeasContext context, IMapper mapper) : ICategoryService
{
public async Task<CategoriesDto> GetCategories()
{
var platforms = await context.Platforms.ToListAsync();
var properties = await context.Properties.ToListAsync();
var tags = await context.Tags.ToListAsync();
var developers = await context.Developers.ToListAsync();
var publishers = await context.Publishers.ToListAsync();
return new()
{
Platforms = mapper.Map<IEnumerable<PlatformDto>>(platforms),
Properties = mapper.Map<IEnumerable<PropertyDto>>(properties),
Tags = mapper.Map<IEnumerable<TagDto>>(tags),
Developers = mapper.Map<IEnumerable<DeveloperDto>>(developers),
Publishers = mapper.Map<IEnumerable<PublisherDto>>(publishers)
};
}
}

View File

@@ -3,11 +3,12 @@ using GameIdeas.Shared.Dto;
using GameIdeas.Shared.Exceptions; using GameIdeas.Shared.Exceptions;
using GameIdeas.Shared.Model; using GameIdeas.Shared.Model;
using GameIdeas.WebAPI.Context; using GameIdeas.WebAPI.Context;
using GameIdeas.WebAPI.Services.Interfaces;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
namespace GameIdeas.WebAPI.Services; namespace GameIdeas.WebAPI.Services;
public class GameService(GameIdeasContext context, IMapper mapper) public class GameService(GameIdeasContext context, IMapper mapper) : IGameService
{ {
public async Task<IEnumerable<GameDto>> GetGames(PaggingDto pagging) public async Task<IEnumerable<GameDto>> GetGames(PaggingDto pagging)
{ {

View File

@@ -0,0 +1,8 @@
using GameIdeas.Shared.Dto;
namespace GameIdeas.WebAPI.Services.Interfaces;
public interface ICategoryService
{
Task<CategoriesDto> GetCategories();
}

View File

@@ -0,0 +1,12 @@
using GameIdeas.Shared.Dto;
namespace GameIdeas.WebAPI.Services.Interfaces;
public interface IGameService
{
Task<IEnumerable<GameDto>> GetGames(PaggingDto pagging);
Task<GameDto> GetGameById(int gameId);
Task<GameDto> CreateGame(GameDto gameDto);
Task<GameDto> UpdateGame(GameDto gameDto);
Task<bool> DeleteGame(int gameId);
}

View File

@@ -1,5 +0,0 @@
namespace GameIdeas.WebAPI.Services;
public class UserService
{
}