Compare commits
18 Commits
8592ecf0f0
...
feature/up
| Author | SHA1 | Date | |
|---|---|---|---|
| 04faddbb9a | |||
| a1cc9dec99 | |||
| 1baa2a73fe | |||
| edd3ac78de | |||
| ae39e15d32 | |||
| 58da2e6843 | |||
| b58ffe10e0 | |||
| e4fe2495ef | |||
| d9d036896d | |||
| f3c9e1d9da | |||
| d3d16493e6 | |||
| 4c1c918bd6 | |||
| c12e31dfeb | |||
| ebb831d741 | |||
| f749d6528e | |||
| 6532d7a6d6 | |||
| 31f124d205 | |||
| a2e93c9438 |
@@ -1,25 +1,30 @@
|
||||
name: Game Ideas build for PR
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, edited, reopened, synchronize ]
|
||||
branches:
|
||||
- 'feature/**'
|
||||
- main
|
||||
pull_request:
|
||||
types: [ opened, edited, reopened, synchronize ]
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build_blazor_app:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v4
|
||||
build_test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup DotNet
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9'
|
||||
- name: Setup .NET 9
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Build Blazor App
|
||||
run: dotnet build ./src/GameIdeas/Client/GameIdeas.BlazorApp/GameIdeas.BlazorApp.csproj
|
||||
|
||||
- name: Build API App
|
||||
run: dotnet build ./src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore ./src/GameIdeas/GameIdeas.sln
|
||||
|
||||
- name: Build API
|
||||
run: dotnet build ./src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj --configuration Release
|
||||
|
||||
- name: Build Blazor App
|
||||
run: dotnet build ./src/GameIdeas/Client/GameIdeas.BlazorApp/GameIdeas.BlazorApp.csproj --configuration Release
|
||||
|
||||
- name: Test API
|
||||
run: dotnet test ./src/GameIdeas/Server/GameIdeas.WebAPI.Tests/GameIdeas.WebAPI.Tests.csproj --configuration Release
|
||||
66
.gitea/workflows/deploy.yaml
Normal file
66
.gitea/workflows/deploy.yaml
Normal file
@@ -0,0 +1,66 @@
|
||||
name: Game Ideas deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main] # Déclenchement sur push sur main
|
||||
workflow_dispatch: # Lancer manuellement
|
||||
|
||||
jobs:
|
||||
build-test-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup .NET 9
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Restore dependencies
|
||||
run: dotnet restore ./src/GameIdeas/GameIdeas.sln
|
||||
|
||||
- name: Build API
|
||||
run: dotnet build ./src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj --configuration Release
|
||||
|
||||
- name: Build Blazor App
|
||||
run: dotnet build ./src/GameIdeas/Client/GameIdeas.BlazorApp/GameIdeas.BlazorApp.csproj --configuration Release
|
||||
|
||||
- name: Test API
|
||||
run: dotnet test ./src/GameIdeas/Server/GameIdeas.WebAPI.Tests/GameIdeas.WebAPI.Tests.csproj --configuration Release
|
||||
|
||||
- name: Publish API
|
||||
run: dotnet publish ./src/GameIdeas/Server/GameIdeas.WebAPI/GameIdeas.WebAPI.csproj --configuration Release --output ./api
|
||||
|
||||
- name: Publish Blazor App
|
||||
run: dotnet publish ./src/GameIdeas/Client/GameIdeas.BlazorApp/GameIdeas.BlazorApp.csproj --configuration Release --output ./blazor
|
||||
|
||||
- name: Copy API to Server
|
||||
uses: appleboy/scp-action@v1
|
||||
with:
|
||||
host: ${{ secrets.DEV_HOST }}
|
||||
username: ${{ secrets.DEV_USERNAME }}
|
||||
password: ${{ secrets.DEV_PASSWORD }}
|
||||
port: ${{ secrets.DEV_PORT }}
|
||||
source: "./api/*"
|
||||
target: "/var/www/"
|
||||
|
||||
- name: Copy Blazor App to Server
|
||||
uses: appleboy/scp-action@v1
|
||||
with:
|
||||
host: ${{ secrets.DEV_HOST }}
|
||||
username: ${{ secrets.DEV_USERNAME }}
|
||||
password: ${{ secrets.DEV_PASSWORD }}
|
||||
port: ${{ secrets.DEV_PORT }}
|
||||
source: "./blazor/*"
|
||||
target: "/var/www/"
|
||||
|
||||
- name: Start API on server
|
||||
uses: appleboy/ssh-action@v1
|
||||
with:
|
||||
host: ${{ secrets.DEV_HOST }}
|
||||
username: ${{ secrets.DEV_USERNAME }}
|
||||
password: ${{ secrets.DEV_PASSWORD }}
|
||||
port: ${{ secrets.DEV_PORT }}
|
||||
script: systemctl restart gameideas.service
|
||||
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
|
||||
├── .gitignore
|
||||
├── .drone.yml (CI/CD)
|
||||
├── .gitea
|
||||
│ ├── build-pr.yaml (CI for Pull Request)
|
||||
│ └── deploy.yaml (CD for deploy on own server)
|
||||
└── src/
|
||||
├── Client/
|
||||
│ └── GameIdeas.BlazorApp
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<UserSecretsId>5637e3c4-2341-4bdb-85ec-c75faeee9847</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -13,17 +13,27 @@ public static class GameHelper
|
||||
throw new ArgumentNullException(nameof(authState), "Authentication state missing");
|
||||
}
|
||||
|
||||
var userId = authState.User.FindFirstValue(ClaimTypes.Sid);
|
||||
|
||||
if (userId == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(authState), "user state missing");
|
||||
}
|
||||
var userId = authState.User.FindFirstValue(ClaimTypes.Sid)
|
||||
?? throw new ArgumentNullException(nameof(authState), "user state missing");
|
||||
|
||||
game.CreationUserId = userId;
|
||||
game.CreationDate = DateTime.Now;
|
||||
}
|
||||
|
||||
public static void UpdateTrackingDto(GameDetailDto game, AuthenticationState authState)
|
||||
{
|
||||
if (authState == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(authState), "Authentication state missing");
|
||||
}
|
||||
|
||||
var userId = authState.User.FindFirstValue(ClaimTypes.Sid)
|
||||
?? throw new ArgumentNullException(nameof(authState), "user state missing");
|
||||
|
||||
game.ModificationUserId = userId;
|
||||
game.ModificationDate = DateTime.Now;
|
||||
}
|
||||
|
||||
public static string GetInterestColor(int interest, int maxInterest)
|
||||
{
|
||||
int firstTier = (int)Math.Floor(0.33 * maxInterest);
|
||||
|
||||
@@ -3,10 +3,4 @@
|
||||
|
||||
<div class="page">
|
||||
@Body
|
||||
</div>
|
||||
|
||||
<div class="background">
|
||||
<span class="orb red"></span>
|
||||
<span class="orb blue"></span>
|
||||
<span class="orb green"></span>
|
||||
</div>
|
||||
@@ -2,47 +2,4 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.orb {
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
z-index: var(--index-orb);
|
||||
}
|
||||
|
||||
.green {
|
||||
width: 80vh;
|
||||
height: 80vh;
|
||||
top: -20vh;
|
||||
background: #315941;
|
||||
filter: blur(30vh);
|
||||
}
|
||||
|
||||
.blue {
|
||||
width: 80vw;
|
||||
height: 80vw;
|
||||
left: 10vw;
|
||||
top: 50vh;
|
||||
background: #3A4156;
|
||||
filter: blur(30vh);
|
||||
}
|
||||
|
||||
.red {
|
||||
width: 100vh;
|
||||
height: 100vh;
|
||||
left: 60vw;
|
||||
top: -40vh;
|
||||
background: #593533;
|
||||
filter: blur(30vh);
|
||||
}
|
||||
|
||||
.background {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background: var(--background);
|
||||
position: fixed;
|
||||
overflow: hidden;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: var(--index-background);
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
@page "/Detail/{GameId:int}"
|
||||
@using GameIdeas.BlazorApp.Helpers
|
||||
@using GameIdeas.BlazorApp.Pages.Games.Components
|
||||
@using GameIdeas.BlazorApp.Shared.Components
|
||||
@using GameIdeas.BlazorApp.Shared.Components.ButtonAdd
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Header
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Interest
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Popup
|
||||
@using GameIdeas.BlazorApp.Shared.Components.ReadMore
|
||||
@using GameIdeas.BlazorApp.Shared.Constants
|
||||
@using GameIdeas.Shared.Constants
|
||||
|
||||
@inherits GameBaseComponent
|
||||
@layout MainLayout
|
||||
|
||||
<HeaderGameIdeas>
|
||||
<div class="button">
|
||||
<ButtonAdd AddTypeChanged="HandleAddClicked" />
|
||||
</div>
|
||||
</HeaderGameIdeas>
|
||||
|
||||
<div class="detail-container">
|
||||
<div class="section flex">
|
||||
<a href="" class="square-button">@Icons.Back</a>
|
||||
<h1 class="header-1">@Game.Title</h1>
|
||||
<Interest Value="Game.Interest" />
|
||||
</div>
|
||||
|
||||
|
||||
<div class="section col-2">
|
||||
<ReadMore Text="@Game.Description" MaxLength="GlobalConstants.MAX_DESCRIPTION_LENGTH" />
|
||||
|
||||
<div class="medias"></div>
|
||||
</div>
|
||||
|
||||
<div class="section dark col-2">
|
||||
<div class="properties">
|
||||
<h2 class="header-2 grd-col-1">@ResourcesKey.Properties</h2>
|
||||
<div class="pills">
|
||||
@foreach (var property in Game.Properties ?? [])
|
||||
{
|
||||
<div class="pill body-lg">@property.Label</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="tags">
|
||||
<h2 class="header-2">@ResourcesKey.Tags</h2>
|
||||
<div class="pills">
|
||||
@foreach (var property in Game.Tags ?? [])
|
||||
{
|
||||
<div class="pill body-lg">@property.Label</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section col-2 first-larger">
|
||||
<div class="additional-informations">
|
||||
<h2 class="header-2">@ResourcesKey.About</h2>
|
||||
<div class="informations">
|
||||
@if (Game.ReleaseDate != null)
|
||||
{
|
||||
<div class="information">
|
||||
<span class="body-sm">@ResourcesKey.ReleaseDate</span>
|
||||
<span class="body-lg">@Game.ReleaseDate?.ToShortDateString()</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Game.StorageSpace != null)
|
||||
{
|
||||
<div class="information">
|
||||
<span class="body-sm">@ResourcesKey.StorageSize</span>
|
||||
<span class="body-lg">@GameHelper.GetFormatedStorageSpace(Game.StorageSpace)</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Game.Developer != null)
|
||||
{
|
||||
<div class="information">
|
||||
<span class="body-sm">@ResourcesKey.Developer</span>
|
||||
<span class="body-lg">@Game.Developer?.Name</span>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (Game.Publisher != null)
|
||||
{
|
||||
<div class="information">
|
||||
<span class="body-sm">@ResourcesKey.Publisher</span>
|
||||
<span class="body-lg">@Game.Publisher?.Name</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="platforms">
|
||||
<h2 class="header-2">@ResourcesKey.Platforms</h2>
|
||||
<div class="pills">
|
||||
@foreach (var platform in Game.Platforms ?? [])
|
||||
{
|
||||
<a class="body-lg pill platform-pill" href="@platform.Url">@platform.Label</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Popup @ref=ManualAddPopup BackdropFilterClicked="HandleBackdropManualAddClicked" Closable=false>
|
||||
<GameCreationForm Categories="Categories" OnSubmit="HandleSubmitNewGame" />
|
||||
</Popup>
|
||||
@@ -0,0 +1,47 @@
|
||||
using GameIdeas.BlazorApp.Shared.Components;
|
||||
using GameIdeas.BlazorApp.Shared.Exceptions;
|
||||
using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Detail;
|
||||
|
||||
public partial class GameDetail : GameBaseComponent
|
||||
{
|
||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||
[Parameter] public int GameId { get; set; }
|
||||
|
||||
private GameDetailDto Game = new();
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await FetchGameDetail();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private void HandleSubmitNewGame()
|
||||
{
|
||||
NavigationManager.NavigateTo("/Games");
|
||||
}
|
||||
|
||||
private async Task FetchGameDetail()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
Game = await GameGateway.GetGameById(GameId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new FetchGameDetailException(ResourcesKey.ErrorFetchDetail);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
.detail-container, .properties-tags {
|
||||
display: grid;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 20px 100px;
|
||||
}
|
||||
|
||||
.col-2 {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 40px;
|
||||
}
|
||||
|
||||
.first-larger {
|
||||
grid-template-columns: 3fr 2fr;
|
||||
}
|
||||
|
||||
.dark {
|
||||
background: rgb(0, 0, 0, 0.4)
|
||||
}
|
||||
|
||||
.header-1, .header-2 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-2 {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.pills, .informations {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 34px;
|
||||
}
|
||||
|
||||
.pills {
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.additional-informations, .platforms {
|
||||
padding: 20px;
|
||||
background: var(--input-secondary);
|
||||
box-shadow: var(--drop-shadow);
|
||||
border-radius: var(--big-radius);
|
||||
}
|
||||
|
||||
.pill {
|
||||
width: fit-content;
|
||||
height: 24px;
|
||||
padding: 0 6px;
|
||||
background: rgb(255, 255, 255, 0.2);
|
||||
border-radius: var(--small-radius);
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.platform-pill {
|
||||
color: var(--violet);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.platform-pill:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.square-button {
|
||||
height: 28px;
|
||||
min-height: 28px;
|
||||
width: 28px;
|
||||
min-width: 28px;
|
||||
border-radius: var(--small-radius);
|
||||
background: var(--input-primary);
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.square-button ::deep svg {
|
||||
fill: var(--white);
|
||||
}
|
||||
|
||||
.square-button:hover ::deep svg {
|
||||
background: var(--input-selected);
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.section {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.col-2 {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.platforms {
|
||||
grid-row: 1;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace GameIdeas.BlazorApp.Pages.Games.Components;
|
||||
|
||||
public enum DetailOptions
|
||||
{
|
||||
Detail,
|
||||
Edit,
|
||||
Delete
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using GameIdeas.Shared.Dto;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
|
||||
using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Games.Components;
|
||||
@@ -6,10 +9,51 @@ namespace GameIdeas.BlazorApp.Pages.Games.Components;
|
||||
public class GameBase : ComponentBase
|
||||
{
|
||||
[Parameter] public GameDto GameDto { get; set; } = new();
|
||||
[Parameter] public EventCallback<GameDto> OnDelete { get; set; } = new();
|
||||
[Parameter] public EventCallback<GameDto> OnEdit { get; set; } = new();
|
||||
[Inject] public NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
protected void HandleDetailClicked()
|
||||
protected SelectParams<DetailOptions, object> SelectParams = default!;
|
||||
protected Select<DetailOptions, object>? SelectOption;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
NavigationManager.NavigateTo($"/Games/Detail/{GameDto.Id}");
|
||||
SelectParams = new()
|
||||
{
|
||||
Items = [DetailOptions.Detail, DetailOptions.Edit, DetailOptions.Delete],
|
||||
GetItemLabel = GetDetailOptionsLabel
|
||||
};
|
||||
}
|
||||
|
||||
protected async Task HandlerSelectValuesChanged(IEnumerable<DetailOptions> detailOptions)
|
||||
{
|
||||
var option = detailOptions.First();
|
||||
switch (option)
|
||||
{
|
||||
case DetailOptions.Detail:
|
||||
NavigationManager.NavigateTo($"/Detail/{GameDto.Id}");
|
||||
break;
|
||||
case DetailOptions.Edit:
|
||||
await OnEdit.InvokeAsync(GameDto);
|
||||
break;
|
||||
case DetailOptions.Delete:
|
||||
await OnDelete.InvokeAsync(GameDto);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
SelectOption?.Close();
|
||||
}
|
||||
|
||||
private string GetDetailOptionsLabel(DetailOptions options)
|
||||
{
|
||||
return options switch
|
||||
{
|
||||
DetailOptions.Detail => ResourcesKey.Detail,
|
||||
DetailOptions.Edit => ResourcesKey.Edit,
|
||||
DetailOptions.Delete => ResourcesKey.Delete,
|
||||
_ => ResourcesKey.Unknown
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@using Blazored.FluentValidation
|
||||
@using GameIdeas.BlazorApp.Shared.Components.CircleLoader
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
|
||||
@using GameIdeas.BlazorApp.Shared.Components.SelectSearch
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Slider
|
||||
@using GameIdeas.Shared.Dto
|
||||
@@ -10,7 +11,7 @@
|
||||
<div class="container">
|
||||
<div class="input-game">
|
||||
<div id="first-label" class="label">@ResourcesKey.Title :</div>
|
||||
<InputText class="title" @bind-Value=GameDto.Title/>
|
||||
<InputText class="title" @bind-Value=GameDto.Title/>
|
||||
</div>
|
||||
<div class="input-game">
|
||||
<div class="label">@ResourcesKey.ReleaseDate :</div>
|
||||
@@ -21,16 +22,16 @@
|
||||
<InputNumber TValue="double?" class="storage" @bind-Value=GameDto.StorageSpace />
|
||||
</div>
|
||||
<div class="input-game">
|
||||
<div class="label">@ResourcesKey.Developers :</div>
|
||||
<div class="label">@ResourcesKey.Developer :</div>
|
||||
<SelectSearch TItem="DeveloperDto" Theme="Theme" GetLabel="@(i => i.Name)" QuickAdd=true
|
||||
Items="Categories?.Developers" @bind-Values=GameDto.Developers
|
||||
AddItem="@(str => new DeveloperDto() { Name = str })" />
|
||||
Items="Categories?.Developers" ValuesChanged="HandleDeveloperChanged" Values="@(GameDto.Developer != null ? [GameDto.Developer] : [])"
|
||||
AddItem="@(str => new DeveloperDto() { Name = str })" SelectType="SelectType.Single" />
|
||||
</div>
|
||||
<div class="input-game">
|
||||
<div class="label">@ResourcesKey.Publishers :</div>
|
||||
<div class="label">@ResourcesKey.Publisher :</div>
|
||||
<SelectSearch TItem="PublisherDto" Theme="Theme" GetLabel="@(i => i.Name)" QuickAdd=true
|
||||
Items="Categories?.Publishers" @bind-Values=GameDto.Publishers
|
||||
AddItem="@(str => new PublisherDto() { Name = str })" />
|
||||
Items="Categories?.Publishers" ValuesChanged="HandlePublisherChanged" Values="@(GameDto.Publisher != null ? [GameDto.Publisher] : [])"
|
||||
AddItem="@(str => new PublisherDto() { Name = str })" SelectType="SelectType.Single" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
@@ -43,21 +44,21 @@
|
||||
<div class="input-game">
|
||||
<div class="label">@ResourcesKey.Properties :</div>
|
||||
<SelectSearch TItem="PropertyDto" Theme="Theme" GetLabel="@(i => i.Label)" QuickAdd=true
|
||||
Items="Categories?.Properties" @bind-Values=GameDto.Properties
|
||||
AddItem="@(str => new PropertyDto() { Label = str })" />
|
||||
Items="Categories?.Properties" @bind-Values=GameDto.Properties
|
||||
AddItem="@(str => new PropertyDto() { Label = str })" />
|
||||
</div>
|
||||
<div class="input-game">
|
||||
<div class="label">@ResourcesKey.Tags :</div>
|
||||
<SelectSearch TItem="TagDto" Theme="Theme" GetLabel="@(i => i.Label)" QuickAdd=true
|
||||
Items="Categories?.Tags" @bind-Values=GameDto.Tags
|
||||
AddItem="@(str => new TagDto() { Label = str })" />
|
||||
Items="Categories?.Tags" @bind-Values=GameDto.Tags
|
||||
AddItem="@(str => new TagDto() { Label = str })" />
|
||||
|
||||
</div>
|
||||
<div class="input-game">
|
||||
<div class="label">@ResourcesKey.Platforms :</div>
|
||||
<SelectSearch TItem="PlatformDto" Theme="Theme" GetLabel="@(i => i.Label)" QuickAdd=true
|
||||
Items="Categories?.Platforms" @bind-Values=GameDto.Platforms
|
||||
AddItem="@(str => new PlatformDto() { Label = str })" />
|
||||
Items="Categories?.Platforms" @bind-Values=GameDto.Platforms
|
||||
AddItem="@(str => new PlatformDto() { Label = str })" />
|
||||
</div>
|
||||
|
||||
@foreach (var platform in GameDto.Platforms ?? [])
|
||||
|
||||
@@ -3,12 +3,13 @@ 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;
|
||||
using GameIdeas.BlazorApp.Shared.Exceptions;
|
||||
using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Microsoft.JSInterop;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Games.Components;
|
||||
|
||||
@@ -20,22 +21,27 @@ public partial class GameCreationForm
|
||||
[CascadingParameter] private Popup? Popup { get; set; }
|
||||
[Parameter] public CategoriesDto? Categories { get; set; }
|
||||
[Parameter] public EventCallback OnSubmit { get; set; }
|
||||
|
||||
[Parameter] public EventCallback OnRender { get; set; }
|
||||
|
||||
private GameDetailDto GameDto = new();
|
||||
private EditContext? EditContext;
|
||||
private readonly SelectTheme Theme = SelectTheme.Creation;
|
||||
private readonly SliderParams SliderParams = new() { Gap = 1, Min = 1, Max = 5 };
|
||||
private bool IsLoading = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
EditContext = new(GameDto);
|
||||
await base.OnInitializedAsync();
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
await Js.InvokeVoidAsync("resizeGameForm");
|
||||
if (firstRender)
|
||||
{
|
||||
await OnRender.InvokeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleOnCancel()
|
||||
@@ -53,16 +59,24 @@ public partial class GameCreationForm
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
int gameId;
|
||||
var authState = await AuthenticationState.GetAuthenticationStateAsync();
|
||||
GameHelper.WriteTrackingDto(GameDto, authState);
|
||||
|
||||
var gameId = await GameGateway.CreateGame(GameDto);
|
||||
|
||||
if (gameId != 0)
|
||||
if (GameDto.Id != null)
|
||||
{
|
||||
Popup?.Close();
|
||||
await OnSubmit.InvokeAsync();
|
||||
gameId = await GameGateway.UpdateGame(GameDto);
|
||||
}
|
||||
else
|
||||
{
|
||||
gameId = await GameGateway.CreateGame(GameDto);
|
||||
}
|
||||
|
||||
if (gameId == 0)
|
||||
{
|
||||
throw new GameCreationException(ResourcesKey.ErrorCreateGame);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -74,5 +88,40 @@ public partial class GameCreationForm
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
Popup?.Close();
|
||||
|
||||
await OnSubmit.InvokeAsync();
|
||||
}
|
||||
|
||||
private void HandlePublisherChanged(List<PublisherDto> pubs)
|
||||
{
|
||||
GameDto.Publisher = pubs.FirstOrDefault();
|
||||
}
|
||||
|
||||
private void HandleDeveloperChanged(List<DeveloperDto> devs)
|
||||
{
|
||||
GameDto.Developer = devs.FirstOrDefault();
|
||||
}
|
||||
|
||||
public async Task SetGameToUpdateAsync(int gameId)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
GameDto = await GameGateway.GetGameById(gameId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new FetchGameDetailException(ResourcesKey.ErrorFetchDetail);
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
EditContext = new(GameDto);
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@
|
||||
grid-column: 2;
|
||||
box-sizing: border-box;
|
||||
color: var(--white);
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
::deep input[type="date"]::-webkit-calendar-picker-indicator {
|
||||
@@ -69,7 +70,7 @@
|
||||
}
|
||||
|
||||
.slider {
|
||||
padding: 0 20px;
|
||||
margin-right: 20px;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
@@ -118,3 +119,20 @@
|
||||
.buttons button:hover {
|
||||
background: var(--violet-selected);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 400px) {
|
||||
.input-game {
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
#label-description {
|
||||
width: auto !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.game-form {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
@using GameIdeas.BlazorApp.Helpers
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Interest
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
|
||||
@using GameIdeas.BlazorApp.Shared.Constants
|
||||
@inherits GameBase
|
||||
|
||||
<div class="row">
|
||||
<img class="icon" src="~/icon.png" />
|
||||
<img class="icon" src="icon.png" />
|
||||
|
||||
<a class="title" href="@($"/Games/Detail/{GameDto.Id}")">@GameDto.Title</a>
|
||||
<a class="title" href="@($"/Detail/{GameDto.Id}")">@GameDto.Title</a>
|
||||
|
||||
<span class="release-date">@(GameDto.ReleaseDate?.ToShortDateString() ?? @ResourcesKey.Unknown)</span>
|
||||
|
||||
@@ -30,12 +33,10 @@
|
||||
|
||||
<span class="storage">@GameHelper.GetFormatedStorageSpace(GameDto.StorageSpace)</span>
|
||||
|
||||
<div class="interest">
|
||||
<span class="value" style="@($"color: var({GameHelper.GetInterestColor(GameDto.Interest, 5)})")">
|
||||
@GameDto.Interest
|
||||
</span>
|
||||
<span class="max-value">/5</span>
|
||||
</div>
|
||||
<Interest Value="GameDto.Interest" />
|
||||
|
||||
<button class="detail">@Icons.Triangle</button>
|
||||
<Select @ref="SelectOption" TItem="DetailOptions" THeader="object" Type="SelectType.Single" Theme="SelectTheme.RowOption"
|
||||
Params="SelectParams" ValuesChanged="HandlerSelectValuesChanged">
|
||||
@Icons.Triangle
|
||||
</Select>
|
||||
</div>
|
||||
@@ -8,10 +8,9 @@
|
||||
box-shadow: var(--drop-shadow);
|
||||
border-radius: var(--big-radius);
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.row * {
|
||||
.row > * {
|
||||
max-height: 64px;
|
||||
height: fit-content;
|
||||
padding: 6px 0;
|
||||
@@ -39,7 +38,8 @@
|
||||
background: var(--input-selected);
|
||||
}
|
||||
|
||||
.release-date, .storage, .max-value {
|
||||
.release-date, .storage {
|
||||
display: block;
|
||||
color: rgb(184, 184, 184);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
background: rgb(255, 255, 255, 0.2);
|
||||
border-radius: var(--small-radius);
|
||||
align-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.platforms, .tags {
|
||||
display: flex;
|
||||
@@ -68,31 +68,37 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.detail {
|
||||
transform: scale(0.6, 1) rotate(-90deg);
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
::deep .button {
|
||||
width: fit-content;
|
||||
transform: rotate(-90deg);
|
||||
transition: transform 0.2s ease-in-out;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
::deep .detail svg {
|
||||
fill: var(--white);
|
||||
}
|
||||
::deep .button svg {
|
||||
fill: var(--white);
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
transform: scale(1, 0.6);
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
::deep .button:hover, ::deep .button.selected {
|
||||
transform: translate(-4px, 2px);
|
||||
}
|
||||
|
||||
.max-value {
|
||||
position: absolute;
|
||||
transform: translate(2px, 10px);
|
||||
}
|
||||
@media screen and (max-width: 700px) {
|
||||
.release-date, .tags, .storage {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.row {
|
||||
grid-template-columns: 48px 3fr 2fr 3fr 30px 30px;
|
||||
grid-template-columns: auto 3fr 3fr 30px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: 700px) and (max-width: 1000px) {
|
||||
.row {
|
||||
grid-template-columns: auto 3fr 2fr 3fr 30px 30px;
|
||||
}
|
||||
|
||||
.tags, .storage {
|
||||
|
||||
@@ -96,16 +96,4 @@
|
||||
.tags, .storage {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes loading {
|
||||
0% {
|
||||
background: rgb(255, 255, 255, 0.05);
|
||||
}
|
||||
50% {
|
||||
background: rgb(255, 255, 255, 0.2);
|
||||
}
|
||||
100% {
|
||||
background: rgb(255, 255, 255, 0.05);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
|
||||
@using GameIdeas.BlazorApp.Shared.Components.BackdropFilter
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
|
||||
@using GameIdeas.BlazorApp.Shared.Components.SelectSearch
|
||||
@using GameIdeas.BlazorApp.Shared.Constants
|
||||
@using GameIdeas.Shared.Dto
|
||||
|
||||
<div class="advanced-filter-container">
|
||||
<NavigationLock OnBeforeInternalNavigation="HandleLocationChanged" />
|
||||
|
||||
<div class="advanced-filter-container" style="@(ExpandedFilter ? "display: flex" : "")">
|
||||
<span class="title">@ResourcesKey.Filters</span>
|
||||
|
||||
<div class="duplicate">
|
||||
@@ -16,17 +20,24 @@
|
||||
<SelectSearch TItem="PropertyDto" Placeholder="@ResourcesKey.Properties" GetLabel="@(p => p.Label)"
|
||||
@bind-Values=GameFilter.Properties @bind-Values:after=HandleValueChanged Theme="Theme" Items="Categories?.Properties" />
|
||||
|
||||
<SelectSearch TItem="DeveloperDto" Placeholder="@ResourcesKey.Developers" GetLabel="@(p => p.Name)"
|
||||
<SelectSearch TItem="DeveloperDto" Placeholder="@ResourcesKey.Developer" GetLabel="@(p => p.Name)"
|
||||
@bind-Values=GameFilter.Developers @bind-Values:after=HandleValueChanged Theme="Theme" Items="Categories?.Developers" />
|
||||
|
||||
<SelectSearch TItem="PublisherDto" Placeholder="@ResourcesKey.Publishers" GetLabel="@(p => p.Name)"
|
||||
<SelectSearch TItem="PublisherDto" Placeholder="@ResourcesKey.Publisher" GetLabel="@(p => p.Name)"
|
||||
@bind-Values=GameFilter.Publishers @bind-Values:after=HandleValueChanged Theme="Theme" Items="Categories?.Publishers" />
|
||||
|
||||
<SelectSearch TItem="int" Placeholder="@ResourcesKey.ReleaseDate" GetLabel="@(p => p.ToString())"
|
||||
@bind-Values=GameFilter.ReleaseYears @bind-Values:after=HandleValueChanged Theme="Theme" Items="Categories?.ReleaseYears" />
|
||||
|
||||
<SelectSearch TItem="int" Placeholder="@ResourcesKey.StorageSize" GetLabel="@GetStorageSpaceLabel"
|
||||
<SelectSearch TItem="int" Placeholder="@ResourcesKey.StorageSize" GetLabel="@GetStorageSpaceLabel" OrderBy="@(item => item.ToString())"
|
||||
@bind-Values=GameFilter.StorageSpaceIds @bind-Values:after=HandleValueChanged Theme="Theme" Items="@(Categories?.StorageSpaces?.Select(stor => stor.Id).ToList())" />
|
||||
|
||||
<span class="title">@ResourcesKey.LastAdd</span>
|
||||
</div>
|
||||
|
||||
<button type="button" class="open-filter" @onclick=HandleExpandFilterAsync>
|
||||
@Icons.Filter
|
||||
</button>
|
||||
|
||||
<BackdropFilter @ref="BackdropFilter" OnClick="HandleBackdropFilterClickedAsync" CloseOnClick="true"
|
||||
AllowBodyScroll="false" Color="BackdropFilterColor.Overlay" />
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
using GameIdeas.BlazorApp.Helpers;
|
||||
using GameIdeas.BlazorApp.Pages.Games.Header;
|
||||
using GameIdeas.BlazorApp.Shared.Components.BackdropFilter;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
|
||||
using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Routing;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Games.Filter;
|
||||
|
||||
@@ -14,6 +15,8 @@ public partial class AdvancedGameFilter
|
||||
[Parameter] public EventCallback<GameFilterParams> GameFilterChanged { get; set; }
|
||||
|
||||
private readonly SelectTheme Theme = SelectTheme.AdvancedFilter;
|
||||
private bool ExpandedFilter;
|
||||
private BackdropFilter? BackdropFilter;
|
||||
|
||||
private async Task HandleValueChanged()
|
||||
{
|
||||
@@ -46,4 +49,24 @@ public partial class AdvancedGameFilter
|
||||
|
||||
throw new ArgumentNullException(ResourcesKey.ErrorStorageSpaceLabel);
|
||||
}
|
||||
private void HandleExpandFilterAsync(Microsoft.AspNetCore.Components.Web.MouseEventArgs args)
|
||||
{
|
||||
ExpandedFilter = true;
|
||||
BackdropFilter?.Show();
|
||||
}
|
||||
|
||||
private void HandleBackdropFilterClickedAsync()
|
||||
{
|
||||
ExpandedFilter = false;
|
||||
}
|
||||
|
||||
private void HandleLocationChanged(LocationChangingContext locationContext)
|
||||
{
|
||||
if (ExpandedFilter)
|
||||
{
|
||||
ExpandedFilter = false;
|
||||
BackdropFilter?.Hide();
|
||||
locationContext.PreventNavigation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,15 +2,39 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
padding-right: 20px;
|
||||
padding-left: 10px;
|
||||
padding: 0 20px;
|
||||
min-height: calc(100vh - 80px);
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
width: 260px;
|
||||
border-left: 2px solid var(--line);
|
||||
z-index: var(--index-content);
|
||||
}
|
||||
|
||||
.open-filter {
|
||||
padding: 0;
|
||||
display: none;
|
||||
position: fixed;
|
||||
outline: none;
|
||||
border: none;
|
||||
background: var(--input-primary);
|
||||
border-radius: 100px;
|
||||
right: 10px;
|
||||
bottom: 10px;
|
||||
overflow: hidden;
|
||||
z-index: var(--index-floating);
|
||||
}
|
||||
|
||||
.open-filter ::deep svg {
|
||||
fill: var(--white);
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.open-filter:hover ::deep svg {
|
||||
background: var(--input-selected);
|
||||
}
|
||||
|
||||
.duplicate {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
@@ -30,3 +54,28 @@
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.advanced-filter-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.open-filter {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.advanced-filter-container {
|
||||
border-radius: var(--big-radius) 0 0 var(--big-radius);
|
||||
background: var(--input-primary);
|
||||
border: none;
|
||||
right: 0;
|
||||
position: fixed;
|
||||
z-index: 800;
|
||||
width: auto;
|
||||
height: auto;
|
||||
left: 25vw;
|
||||
bottom: 0;
|
||||
top: 0;
|
||||
padding: 60px 20px;
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,7 @@
|
||||
|
||||
<div class="slider-container">
|
||||
<SliderRange Params="SliderRangeParams"
|
||||
@bind-Max="Value.MaxInterest" @bind-Max:after="HandleValueChanged"
|
||||
@bind-Min="Value.MinInterest" @bind-Min:after="HandleValueChanged" />
|
||||
@bind-Value="Value.Interest" @bind-Value:after="HandleValueChanged" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,21 +2,16 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
margin: 0 8px;
|
||||
margin: 0 auto;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
width: 150px;
|
||||
min-width: 150px;
|
||||
min-width: 50px;
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
.slider-container {
|
||||
width: 150px;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.select-container {
|
||||
.slider-container, .select-container {
|
||||
width: 150px;
|
||||
min-width: 150px;
|
||||
}
|
||||
@@ -63,6 +58,16 @@
|
||||
padding-right: 1px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.slider-container {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.square-button:first-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.select-container {
|
||||
display: none;
|
||||
|
||||
@@ -12,8 +12,7 @@ public class GameFilterParams
|
||||
public List<TagDto>? Tags { get; set; }
|
||||
public List<PublisherDto>? Publishers { get; set; }
|
||||
public List<DeveloperDto>? Developers { get; set; }
|
||||
public int MinInterest { get; set; } = 1;
|
||||
public int MaxInterest { get; set; } = 5;
|
||||
public MinMaxDto Interest { get; set; } = new() { Min = 1, Max = 5 };
|
||||
public List<int>? ReleaseYears { get; set; }
|
||||
public List<int>? StorageSpaceIds { get; set; }
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
@page "/Games"
|
||||
@using GameIdeas.BlazorApp.Layouts
|
||||
@using GameIdeas.BlazorApp.Pages.Games.Components
|
||||
@using GameIdeas.BlazorApp.Pages.Games.Filter
|
||||
@using GameIdeas.BlazorApp.Pages.Games.Header
|
||||
@using GameIdeas.BlazorApp.Shared.Components
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Popup
|
||||
@using GameIdeas.Resources
|
||||
|
||||
@layout MainLayout
|
||||
|
||||
<PageTitle>@ResourcesKey.GamesIdeas</PageTitle>
|
||||
|
||||
<GameHeader AddTypeChanged="HandleAddClicked">
|
||||
<GameFilter Categories="Categories"
|
||||
@bind-DisplayType=DisplayType
|
||||
Value=GameFilter ValueChanged="HandleFilterChanged" />
|
||||
</GameHeader>
|
||||
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
@if (!IsLoading)
|
||||
{
|
||||
@foreach (var game in GamesDto)
|
||||
{
|
||||
<GameRow GameDto="game" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@for (int i = 0; i < 20; i++)
|
||||
{
|
||||
<GameRowSkeleton />
|
||||
}
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<AdvancedGameFilter GameFilter=GameFilter GameFilterChanged="HandleFilterChanged" Categories="Categories" />
|
||||
</div>
|
||||
|
||||
<Popup @ref=ManualAddPopup BackdropFilterClicked="HandleBackdropManualAddClicked" Closable=false>
|
||||
<GameCreationForm Categories="Categories" OnSubmit="() => HandleFetchDatas()" />
|
||||
</Popup>
|
||||
@@ -1,78 +0,0 @@
|
||||
using GameIdeas.BlazorApp.Pages.Games.Filter;
|
||||
using GameIdeas.BlazorApp.Pages.Games.Gateways;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Popup;
|
||||
using GameIdeas.BlazorApp.Shared.Models;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using GameIdeas.Shared.Enum;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Games;
|
||||
|
||||
public partial class Game
|
||||
{
|
||||
[Inject] private IGameGateway GameGateway { get; set; } = default!;
|
||||
|
||||
private DisplayType DisplayType = DisplayType.List;
|
||||
private GameFilterParams GameFilter = new();
|
||||
private Popup? ManualAddPopup;
|
||||
private bool IsLoading = false;
|
||||
private CategoriesDto? Categories;
|
||||
private IEnumerable<GameDto> GamesDto = [];
|
||||
private int CurrentPage;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
CurrentPage = 1;
|
||||
GameFilter.SortType = Filter.GameFilter.SortTypes
|
||||
.First(st => st.SortType == SortType.Ascending);
|
||||
|
||||
GameFilter.SortProperty= Filter.GameFilter.GameProperties
|
||||
.First(gp => gp.PropertyName == nameof(GameIdeas.Shared.Model.Game.Title));
|
||||
|
||||
await HandleFetchDatas();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private void HandleAddClicked(AddType addType)
|
||||
{
|
||||
switch (addType)
|
||||
{
|
||||
case AddType.Manual:
|
||||
ManualAddPopup?.Open();
|
||||
break;
|
||||
case AddType.Auto:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
private void HandleBackdropManualAddClicked()
|
||||
{
|
||||
ManualAddPopup?.Close();
|
||||
}
|
||||
private async Task HandleFetchDatas(bool loadCategories = true, bool displayLoader = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = displayLoader;
|
||||
|
||||
if (loadCategories)
|
||||
Categories = await GameGateway.FetchCategories();
|
||||
|
||||
GamesDto = await GameGateway.FetchGames(GameFilter, CurrentPage);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}
|
||||
private async Task HandleFilterChanged(GameFilterParams args)
|
||||
{
|
||||
GameFilter = args;
|
||||
await HandleFetchDatas(loadCategories: false, displayLoader: false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
@page "/Games"
|
||||
@using GameIdeas.BlazorApp.Pages.Games.Components
|
||||
@using GameIdeas.BlazorApp.Pages.Games.Filter
|
||||
@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.Components
|
||||
@using GameIdeas.Resources
|
||||
@inherits GameBaseComponent
|
||||
@layout MainLayout
|
||||
|
||||
<PageTitle>@ResourcesKey.GamesIdeas</PageTitle>
|
||||
|
||||
<HeaderGameIdeas>
|
||||
<GameFilter Categories="Categories"
|
||||
@bind-DisplayType=DisplayType
|
||||
Value=GameFilter ValueChanged="HandleFilterChanged" />
|
||||
<ButtonAdd AddTypeChanged="HandleAddClicked" />
|
||||
</HeaderGameIdeas>
|
||||
|
||||
<div class="container">
|
||||
<div class="content">
|
||||
@if (!IsLoading)
|
||||
{
|
||||
@if (GamesDto.NumberOfGames != 0)
|
||||
{
|
||||
<div class="games-number">@string.Format(ResourcesKey.GamesNumberFormat, GamesDto.NumberOfGames)</div>
|
||||
|
||||
@foreach (var game in GamesDto.Games)
|
||||
{
|
||||
<GameRow GameDto="game" OnDelete="HandleDeleteGame" OnEdit="HandleEditGame" />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="no-games">@ResourcesKey.NoGames</div>
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@for (int i = 0; i < 20; i++)
|
||||
{
|
||||
<GameRowSkeleton />
|
||||
}
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<AdvancedGameFilter GameFilter=GameFilter GameFilterChanged="HandleFilterChanged" Categories="Categories" />
|
||||
</div>
|
||||
|
||||
<Popup @ref=ManualAddPopup BackdropFilterClicked="HandleBackdropManualAddClicked" Closable=false>
|
||||
<GameCreationForm @ref="CreationForm" Categories="Categories" OnSubmit="HandleOnSubmitGame" OnRender="HandleRenderCreationForm" />
|
||||
</Popup>
|
||||
|
||||
<Popup @ref=DeletePopup Closable=false>
|
||||
<ConfirmDelete OnCancel="HandleCancelPopupClicked" OnConfirm="HandleRemoveGame" />
|
||||
</Popup>
|
||||
@@ -0,0 +1,122 @@
|
||||
using GameIdeas.BlazorApp.Pages.Games.Components;
|
||||
using GameIdeas.BlazorApp.Pages.Games.Filter;
|
||||
using GameIdeas.BlazorApp.Shared.Components;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Popup;
|
||||
using GameIdeas.BlazorApp.Shared.Models;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using GameIdeas.Shared.Enum;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Games;
|
||||
|
||||
public partial class Games : GameBaseComponent
|
||||
{
|
||||
private DisplayType DisplayType = DisplayType.List;
|
||||
private GameFilterParams GameFilter = new();
|
||||
private GameListDto GamesDto = new();
|
||||
private int CurrentPage;
|
||||
private Popup? DeletePopup;
|
||||
private GameDto? GameToDelete;
|
||||
private int? GameIdToUpdate;
|
||||
private GameCreationForm? CreationForm;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
CurrentPage = 1;
|
||||
GameFilter.SortType = Filter.GameFilter.SortTypes
|
||||
.First(st => st.SortType == SortType.Ascending);
|
||||
|
||||
GameFilter.SortProperty = Filter.GameFilter.GameProperties
|
||||
.First(gp => gp.PropertyName == nameof(GameIdeas.Shared.Model.Game.Title));
|
||||
|
||||
await HandleFetchDatas();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private async Task HandleFetchDatas(bool displayLoader = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = displayLoader;
|
||||
StateHasChanged();
|
||||
|
||||
GamesDto = await GameGateway.FetchGames(GameFilter, CurrentPage);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
private async Task HandleFilterChanged(GameFilterParams args)
|
||||
{
|
||||
GameFilter = args;
|
||||
await HandleFetchDatas(false);
|
||||
}
|
||||
|
||||
private void HandleDeleteGame(GameDto args)
|
||||
{
|
||||
DeletePopup?.Open();
|
||||
GameToDelete = args;
|
||||
}
|
||||
|
||||
private void HandleCancelPopupClicked()
|
||||
{
|
||||
DeletePopup?.Close();
|
||||
GameToDelete = null;
|
||||
}
|
||||
|
||||
private void HandleEditGame(GameDto args)
|
||||
{
|
||||
if (args.Id == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GameIdToUpdate = args.Id;
|
||||
|
||||
ManualAddPopup?.Open();
|
||||
}
|
||||
|
||||
private async Task HandleRemoveGame()
|
||||
{
|
||||
DeletePopup?.Close();
|
||||
|
||||
if (GameToDelete?.Id == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
await GameGateway.DeleteGame(GameToDelete?.Id ?? 0);
|
||||
await HandleFetchDatas(false);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
|
||||
GameToDelete = null;
|
||||
}
|
||||
private async Task HandleRenderCreationForm()
|
||||
{
|
||||
if (GameIdToUpdate != null && CreationForm != null)
|
||||
{
|
||||
await CreationForm.SetGameToUpdateAsync(GameIdToUpdate ?? 0);
|
||||
}
|
||||
|
||||
GameIdToUpdate = null;
|
||||
}
|
||||
private async Task HandleOnSubmitGame()
|
||||
{
|
||||
await HandleFetchDatas(false);
|
||||
await HandleFetchCategories();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 240px;
|
||||
display: flex;
|
||||
padding: 20px 0;
|
||||
height: fit-content;
|
||||
}
|
||||
@@ -11,4 +10,5 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class GameGateway(IHttpClientService httpClientService) : IGameGateway
|
||||
try
|
||||
{
|
||||
var result = await httpClientService.FetchDataAsync<CategoriesDto>(Endpoints.Category.AllCategories);
|
||||
|
||||
|
||||
return result ?? throw new InvalidOperationException(ResourcesKey.ErrorFetchCategories);
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -35,7 +35,7 @@ public class GameGateway(IHttpClientService httpClientService) : IGameGateway
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<GameDto>> FetchGames(GameFilterParams filterParams, int currentPage)
|
||||
public async Task<GameListDto> FetchGames(GameFilterParams filterParams, int currentPage)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -43,8 +43,8 @@ public class GameGateway(IHttpClientService httpClientService) : IGameGateway
|
||||
{
|
||||
CurrentPage = currentPage,
|
||||
Title = filterParams.Title,
|
||||
MaxInterest = filterParams.MaxInterest,
|
||||
MinInterest = filterParams.MinInterest,
|
||||
MaxInterest = filterParams.Interest.Max,
|
||||
MinInterest = filterParams.Interest.Min,
|
||||
StorageSpaces = filterParams.StorageSpaceIds,
|
||||
DeveloperIds = filterParams.Developers?.Select(d => d.Id ?? 0).ToList(),
|
||||
PublisherIds = filterParams.Publishers?.Select(d => d.Id ?? 0).ToList(),
|
||||
@@ -52,9 +52,25 @@ public class GameGateway(IHttpClientService httpClientService) : IGameGateway
|
||||
PropertyIds = filterParams.Properties?.Select(d => d.Id ?? 0).ToList(),
|
||||
ReleaseYears = filterParams.ReleaseYears,
|
||||
TagIds = filterParams.Tags?.Select(d => d.Id ?? 0).ToList(),
|
||||
SortPropertyName = filterParams.SortProperty?.PropertyName,
|
||||
SortType = filterParams.SortType?.SortType
|
||||
};
|
||||
|
||||
var result = await httpClientService.FetchDataAsync<IEnumerable<GameDto>>(Endpoints.Game.Fetch(filter));
|
||||
var result = await httpClientService.FetchDataAsync<GameListDto>(Endpoints.Game.Fetch(filter));
|
||||
return result ?? throw new InvalidOperationException(ResourcesKey.ErrorFetchGames);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new GameNotFoundException(ResourcesKey.ErrorFetchGames);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<GameDetailDto> GetGameById(int gameId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await httpClientService.FetchDataAsync<GameDetailDto>(Endpoints.Game.FetchById(gameId));
|
||||
|
||||
return result ?? throw new InvalidOperationException(ResourcesKey.ErrorFetchGames);
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -62,4 +78,28 @@ public class GameGateway(IHttpClientService httpClientService) : IGameGateway
|
||||
throw new CategoryNotFoundException(ResourcesKey.ErrorFetchGames);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteGame(int gameIdToDelete)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClientService.DeleteAsync<bool>(Endpoints.Game.Delete(gameIdToDelete));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new GameDeletionException(ResourcesKey.ErrorDeleteGame);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> UpdateGame(GameDetailDto gameDto)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClientService.PutAsync<int>(Endpoints.Game.Update, gameDto);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new GameUpdateException(ResourcesKey.ErrorUpdateGame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,5 +7,8 @@ public interface IGameGateway
|
||||
{
|
||||
Task<CategoriesDto> FetchCategories();
|
||||
Task<int> CreateGame(GameDetailDto game);
|
||||
Task<IEnumerable<GameDto>> FetchGames(GameFilterParams filter, int currentPage);
|
||||
Task<GameListDto> FetchGames(GameFilterParams filter, int currentPage);
|
||||
Task<GameDetailDto> GetGameById(int gameId);
|
||||
Task<bool> DeleteGame(int gameIdToDelete);
|
||||
Task<int> UpdateGame(GameDetailDto gameDto);
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
@using GameIdeas.BlazorApp.Pages.Games
|
||||
@using GameIdeas.BlazorApp.Pages.User
|
||||
@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">
|
||||
<div class="icon-container" @onclick="HandleIconClicked">
|
||||
<img src="icon.png" alt="Game Ideas">
|
||||
</div>
|
||||
|
||||
@ChildContent
|
||||
|
||||
<div class="account-add-container">
|
||||
<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>
|
||||
@@ -0,0 +1,10 @@
|
||||
@page "/"
|
||||
|
||||
@inject NavigationManager navigationManager
|
||||
|
||||
@code {
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
navigationManager.NavigateTo("/Games");
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
@using Blazored.FluentValidation
|
||||
|
||||
<EditForm EditContext="EditContext" OnSubmit="HandleLoginSubmit">
|
||||
<FluentValidationValidator />
|
||||
<FluentValidationValidator Validator="Validator" />
|
||||
<div class="login-form">
|
||||
<div class="login-field">
|
||||
<div class="input-title">@ResourcesKey.EnterUsername</div>
|
||||
@@ -10,7 +10,7 @@
|
||||
</div>
|
||||
<div class="login-field">
|
||||
<div class="input-title">@ResourcesKey.EnterPassword</div>
|
||||
<InputText class="input-text"
|
||||
<InputText class="input-text" type="password"
|
||||
@bind-Value="UserDto.Password" />
|
||||
</div>
|
||||
<div class="login-field">
|
||||
@@ -1,18 +1,18 @@
|
||||
using GameIdeas.BlazorApp.Pages.User.Gateways;
|
||||
using GameIdeas.BlazorApp.Pages.UserMenu.Gateways;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.User.Components;
|
||||
namespace GameIdeas.BlazorApp.Pages.UserMenu.Components;
|
||||
|
||||
public partial class Login
|
||||
{
|
||||
[Parameter] public IAuthGateway AuthGateway { get; set; } = default!;
|
||||
|
||||
private EditContext? EditContext;
|
||||
private UserDto UserDto = new();
|
||||
private readonly UserDto UserDto = new();
|
||||
private bool IsLoading = false;
|
||||
|
||||
private readonly LoginValidator Validator = new();
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
EditContext = new EditContext(UserDto);
|
||||
@@ -28,6 +28,8 @@ public partial class Login
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
await AuthGateway.Login(UserDto);
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -50,6 +50,6 @@
|
||||
border-radius: 50%;
|
||||
border: 3px solid rgba(0, 0, 0, 0.2);
|
||||
border-top-color: var(--white);
|
||||
animation: loading 1s linear infinite;
|
||||
animation: spin 1s linear infinite;
|
||||
justify-self: center;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using FluentValidation;
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.User.Components;
|
||||
namespace GameIdeas.BlazorApp.Pages.UserMenu.Components;
|
||||
|
||||
public class LoginValidator : AbstractValidator<UserDto>
|
||||
{
|
||||
@@ -5,7 +5,7 @@ using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.User.Gateways;
|
||||
namespace GameIdeas.BlazorApp.Pages.UserMenu.Gateways;
|
||||
|
||||
public class AuthGateway(IHttpClientService httpClient,
|
||||
AuthenticationStateProvider stateProvider) : IAuthGateway
|
||||
@@ -14,8 +14,10 @@ public class AuthGateway(IHttpClientService httpClient,
|
||||
{
|
||||
try
|
||||
{
|
||||
var token = await httpClient.PostAsync<TokenDto>(Endpoints.Auth.Login, userDto);
|
||||
await ((JwtAuthenticationStateProvider)stateProvider).NotifyUserAuthenticationAsync(token!.Token!);
|
||||
var token = await httpClient.PostAsync<TokenDto>(Endpoints.Auth.Login, userDto)
|
||||
?? throw new InvalidOperationException("Could not retrieve token");
|
||||
|
||||
await ((JwtAuthenticationStateProvider)stateProvider).NotifyUserAuthenticationAsync(token);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -1,6 +1,6 @@
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.User.Gateways;
|
||||
namespace GameIdeas.BlazorApp.Pages.UserMenu.Gateways;
|
||||
|
||||
public interface IAuthGateway
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
@using GameIdeas.BlazorApp.Pages.User.Components
|
||||
@using GameIdeas.BlazorApp.Pages.UserMenu.Components
|
||||
@using GameIdeas.BlazorApp.Shared.Components.BackdropFilter
|
||||
@using GameIdeas.BlazorApp.Shared.Constants
|
||||
@using GameIdeas.Shared.Constants
|
||||
@@ -23,9 +23,9 @@
|
||||
|
||||
<AuthorizeView Roles="@GlobalConstants.ADMINISTRATOR">
|
||||
<Authorized>
|
||||
<div class="menu-element">
|
||||
<a class="menu-element" href="/Users">
|
||||
@ResourcesKey.UserManager
|
||||
</div>
|
||||
</a>
|
||||
<span class="line"></span>
|
||||
</Authorized>
|
||||
</AuthorizeView>
|
||||
@@ -1,20 +1,20 @@
|
||||
using GameIdeas.BlazorApp.Pages.User.Gateways;
|
||||
using GameIdeas.BlazorApp.Services;
|
||||
using GameIdeas.BlazorApp.Pages.UserMenu.Gateways;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.User;
|
||||
namespace GameIdeas.BlazorApp.Pages.UserMenu;
|
||||
|
||||
public partial class UserMenu
|
||||
{
|
||||
[Inject] private IAuthGateway AuthGateway { get; set; } = default!;
|
||||
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
|
||||
|
||||
private bool ContentVisile = false;
|
||||
|
||||
private async Task HandleLogoutClicked()
|
||||
{
|
||||
await AuthGateway.Logout();
|
||||
ContentVisile = false;
|
||||
await AuthGateway.Logout();
|
||||
NavigationManager.NavigateTo("/Games");
|
||||
}
|
||||
|
||||
private void HandleAccountClicked()
|
||||
@@ -36,6 +36,8 @@
|
||||
}
|
||||
|
||||
.menu-element {
|
||||
color: var(--white);
|
||||
text-decoration: none;
|
||||
height: 32px;
|
||||
padding: 0 20px;
|
||||
align-content: center;
|
||||
@@ -0,0 +1,37 @@
|
||||
@using Blazored.FluentValidation
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
|
||||
@using GameIdeas.BlazorApp.Shared.Constants
|
||||
@using GameIdeas.Shared.Dto
|
||||
|
||||
<EditForm EditContext="EditContext" OnSubmit="HandleSubmitClicked">
|
||||
<FluentValidationValidator Validator="Validator" />
|
||||
<div class="row">
|
||||
<div class="icon">
|
||||
@Icons.Account
|
||||
</div>
|
||||
<div class="name">
|
||||
<InputText class="input-name" @bind-Value="@User.Username" disabled="@(!IsEditing)" />
|
||||
</div>
|
||||
<div class="password">
|
||||
<InputText type="password" class="input-password" placeholder="********" @bind-Value="@User.Password" disabled="@(!IsEditing)" />
|
||||
</div>
|
||||
<div class="role">
|
||||
<Select TItem="RoleDto" THeader="object" DisableClicked=!IsEditing Type="SelectType.Single"
|
||||
Theme="SelectTheme.Single" Values="Roles" ValuesChanged="HandleValuesChanged" Params="SelectRoleParams">
|
||||
<span class="role-label @(!IsEditing ? "disabled" : "")">@(User.Role?.Name ?? ResourcesKey.Unknown)</span>
|
||||
</Select>
|
||||
</div>
|
||||
<div class="buttons">
|
||||
@if (CanDelete)
|
||||
{
|
||||
<button type="button" class="remove" @onclick="HandleRemoveClicked">@Icons.Bin</button>
|
||||
}
|
||||
@if (CanEdit)
|
||||
{
|
||||
<button type="button" class="edit @(IsEditing ? "selected" : "")" @onclick="HandleEditClicked">@Icons.Pen</button>
|
||||
}
|
||||
<button type="submit" class="submit" style="display: @(IsEditing ? "block" : "none");">@Icons.Check</button>
|
||||
</div>
|
||||
</div>
|
||||
</EditForm>
|
||||
@@ -0,0 +1,81 @@
|
||||
using FluentValidation;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users.Components;
|
||||
|
||||
public partial class UserRow
|
||||
{
|
||||
[Parameter] public UserDto User { get; set; } = new();
|
||||
[Parameter] public List<RoleDto> Roles { get; set; } = [];
|
||||
[Parameter] public bool CanEdit { get; set; } = true;
|
||||
[Parameter] public bool CanDelete { get; set; } = true;
|
||||
[Parameter] public bool IsEditing { get; set; } = false;
|
||||
[Parameter] public EventCallback<UserDto> OnRemove { get; set; }
|
||||
[Parameter] public EventCallback<UserDto> OnSubmit { get; set; }
|
||||
[Parameter] public IValidator Validator { get; set; } = default!;
|
||||
|
||||
private SelectParams<RoleDto, object> SelectRoleParams = new();
|
||||
private EditContext? EditContext;
|
||||
private UserDto? OriginalUser;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
EditContext = new(User);
|
||||
OriginalUser = new()
|
||||
{
|
||||
Username = User.Username,
|
||||
Role = User.Role
|
||||
};
|
||||
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
SelectRoleParams = new()
|
||||
{
|
||||
GetItemLabel = role => role.Name,
|
||||
Items = Roles
|
||||
};
|
||||
|
||||
base.OnParametersSet();
|
||||
}
|
||||
|
||||
private void HandleValuesChanged(IEnumerable<RoleDto> roles)
|
||||
{
|
||||
User.Role = roles.FirstOrDefault();
|
||||
}
|
||||
|
||||
private async Task HandleSubmitClicked()
|
||||
{
|
||||
if (EditContext?.Validate() == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsEditing = false;
|
||||
await OnSubmit.InvokeAsync(User);
|
||||
User.Password = null;
|
||||
}
|
||||
|
||||
|
||||
private void HandleEditClicked()
|
||||
{
|
||||
if (IsEditing)
|
||||
{
|
||||
User.Username = OriginalUser?.Username;
|
||||
User.Role = OriginalUser?.Role;
|
||||
User.Password = null;
|
||||
}
|
||||
|
||||
IsEditing = !IsEditing;
|
||||
}
|
||||
|
||||
private async Task HandleRemoveClicked()
|
||||
{
|
||||
await OnRemove.InvokeAsync(User);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
.row {
|
||||
height: 64px;
|
||||
display: grid;
|
||||
grid-template-columns: 48px 1fr 1fr 1fr 100px;
|
||||
grid-gap: 8px;
|
||||
padding: 0 8px;
|
||||
background: var(--input-secondary);
|
||||
box-shadow: var(--drop-shadow);
|
||||
border-radius: var(--big-radius);
|
||||
}
|
||||
|
||||
.row > * {
|
||||
align-content: center;
|
||||
max-width: 160px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.icon ::deep svg {
|
||||
fill: var(--line);
|
||||
}
|
||||
|
||||
::deep .input-name,
|
||||
::deep .input-name[disabled],
|
||||
::deep .input-password,
|
||||
::deep .input-password[disabled],
|
||||
::deep .input-password::placeholder {
|
||||
color: var(--white);
|
||||
max-width: 160px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
::deep .input-name,
|
||||
::deep .input-password,
|
||||
.role-label {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
border-radius: var(--small-radius);
|
||||
border: solid 1px var(--line);
|
||||
background: rgb(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
::deep .input-name[disabled],
|
||||
::deep .input-password[disabled],
|
||||
.disabled {
|
||||
border: none;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.role-label {
|
||||
display: block;
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
height:auto;
|
||||
justify-content: end;
|
||||
}
|
||||
|
||||
.buttons > * {
|
||||
border: none;
|
||||
outline: none;
|
||||
margin: auto 0;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
background: none;
|
||||
border-radius: var(--small-radius);
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
.buttons > *:hover, .selected {
|
||||
background: var(--input-selected)
|
||||
}
|
||||
|
||||
.remove ::deep svg {
|
||||
fill: var(--red);
|
||||
}
|
||||
|
||||
.edit ::deep svg {
|
||||
fill: var(--yellow);
|
||||
}
|
||||
|
||||
.submit ::deep svg {
|
||||
fill: var(--green);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.row {
|
||||
height: 104px;
|
||||
display: grid;
|
||||
grid-template-rows: 48px 48px;
|
||||
grid-template-columns: 48px 1fr 100px;
|
||||
}
|
||||
|
||||
.role {
|
||||
grid-column: 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
@using GameIdeas.BlazorApp.Shared.Constants
|
||||
|
||||
<div class="row">
|
||||
<div class="icon">@Icons.Account</div>
|
||||
<div class="name pill"></div>
|
||||
<div class="password pill"></div>
|
||||
<div class="role pill"></div>
|
||||
<div class="buttons">
|
||||
<span class="remove">@Icons.Bin</span>
|
||||
<span class="edit">@Icons.Pen</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,56 @@
|
||||
.row {
|
||||
height: 64px;
|
||||
display: grid;
|
||||
grid-template-columns: 48px 1fr 1fr 1fr auto;
|
||||
grid-gap: 8px;
|
||||
padding: 0 8px;
|
||||
background: var(--input-secondary);
|
||||
box-shadow: var(--drop-shadow);
|
||||
border-radius: var(--big-radius);
|
||||
align-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.icon ::deep svg {
|
||||
fill: var(--line);
|
||||
}
|
||||
|
||||
.pill {
|
||||
animation: loading 3s ease infinite;
|
||||
align-self: center;
|
||||
height: 28px;
|
||||
border-radius: var(--small-radius);
|
||||
box-sizing: border-box;
|
||||
max-width: 180px;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 8px;
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.buttons > * {
|
||||
margin: auto;
|
||||
height: 28px;
|
||||
width: 28px;
|
||||
background: none;
|
||||
border-radius: var(--small-radius);
|
||||
padding: 2px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.remove ::deep svg {
|
||||
fill: var(--red);
|
||||
}
|
||||
|
||||
.edit ::deep svg {
|
||||
fill: var(--yellow);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using FluentValidation;
|
||||
using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users.Components;
|
||||
|
||||
public class UserCreateValidator : AbstractValidator<UserDto>
|
||||
{
|
||||
public UserCreateValidator()
|
||||
{
|
||||
RuleFor(user => user.Username)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
|
||||
RuleFor(user => user.Password)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
|
||||
RuleFor(user => user.Role)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
}
|
||||
}
|
||||
|
||||
public class UserUpdateValidator : AbstractValidator<UserDto>
|
||||
{
|
||||
public UserUpdateValidator()
|
||||
{
|
||||
When(user => user.Password == null && user.Role == null, () =>
|
||||
{
|
||||
RuleFor(user => user.Username)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
});
|
||||
|
||||
When(user => user.Username == null && user.Role == null, () =>
|
||||
{
|
||||
RuleFor(user => user.Password)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
});
|
||||
|
||||
When(user => user.Username == null && user.Password == null, () =>
|
||||
{
|
||||
RuleFor(user => user.Role)
|
||||
.NotEmpty()
|
||||
.WithMessage(ResourcesKey.MissingField);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users.Filters;
|
||||
|
||||
public class UserFilterParams
|
||||
{
|
||||
public string? Name { get; set; }
|
||||
public List<RoleDto>? Roles { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using GameIdeas.BlazorApp.Pages.Users.Filters;
|
||||
using GameIdeas.Shared.Dto;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users.Gateways;
|
||||
|
||||
public interface IUserGateway
|
||||
{
|
||||
Task<UserListDto> GetUsers(UserFilterParams filterParams, int currentPage);
|
||||
Task<IEnumerable<RoleDto>> GetRoles();
|
||||
Task<IdDto> CreateUser(UserDto user);
|
||||
Task<IdDto> UpdateUser(UserDto user);
|
||||
Task<IdDto> DeleteUser(string userId);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
using GameIdeas.BlazorApp.Pages.Users.Filters;
|
||||
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.Users.Gateways;
|
||||
|
||||
public class UserGateway(IHttpClientService httpClient) : IUserGateway
|
||||
{
|
||||
public async Task<IdDto> CreateUser(UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.PostAsync<IdDto>(Endpoints.User.Create, user)
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorCreateUser);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new UserCreationException(ResourcesKey.ErrorCreateUser);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IdDto> DeleteUser(string userId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.DeleteAsync<IdDto>(Endpoints.User.Delete(userId))
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorDeleteUser);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new UserCreationException(ResourcesKey.ErrorDeleteUser);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RoleDto>> GetRoles()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.FetchDataAsync<IEnumerable<RoleDto>>(Endpoints.User.Roles)
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorFetchRoles);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new RoleNotFoundException(ResourcesKey.ErrorFetchRoles);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<UserListDto> GetUsers(UserFilterParams filterParams, int currentPage)
|
||||
{
|
||||
try
|
||||
{
|
||||
UserFilterDto filter = new()
|
||||
{
|
||||
CurrentPage = currentPage,
|
||||
Name = filterParams.Name,
|
||||
RoleIds = filterParams.Roles?.Select(r => r.Id)
|
||||
};
|
||||
|
||||
var url = Endpoints.User.Fetch(filter);
|
||||
return await httpClient.FetchDataAsync<UserListDto>(url)
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorFetchUsers);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new UserNotFoundException(ResourcesKey.ErrorFetchUsers);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IdDto> UpdateUser(UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await httpClient.PutAsync<IdDto>(Endpoints.User.Update(user.Id ?? string.Empty), user)
|
||||
?? throw new InvalidOperationException(ResourcesKey.ErrorUpdateUser);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new UserCreationException(ResourcesKey.ErrorUpdateUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
@page "/Users"
|
||||
@using GameIdeas.BlazorApp.Pages.Users.Components
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Header
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Popup
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Popup.Components
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Search
|
||||
@using GameIdeas.BlazorApp.Shared.Components.Select.Models
|
||||
@using GameIdeas.BlazorApp.Shared.Components.SelectSearch
|
||||
@using GameIdeas.Shared.Dto
|
||||
|
||||
@layout MainLayout
|
||||
|
||||
<PageTitle>@ResourcesKey.GamesIdeas</PageTitle>
|
||||
|
||||
<HeaderGameIdeas>
|
||||
<div class="header-content">
|
||||
<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
|
||||
Items="Roles.ToList()" GetLabel="@(role => role.Name)" Theme="SelectTheme.Filter" />
|
||||
</div>
|
||||
</HeaderGameIdeas>
|
||||
|
||||
<div class="container">
|
||||
<UserRow User="UserAdd" Roles="Roles.ToList()" CanEdit="false" IsEditing="true" OnRemove="HandleResetUser" OnSubmit="HandleSubmitUser" Validator="@(new UserCreateValidator())" />
|
||||
|
||||
<span class="line"></span>
|
||||
|
||||
<div class="content">
|
||||
@if (!IsLoading)
|
||||
{
|
||||
@if (UserList.UsersCount != 0)
|
||||
{
|
||||
<div class="user-number">@string.Format(ResourcesKey.UsersNumberFormat, UserList.UsersCount)</div>
|
||||
|
||||
@foreach (var user in UserList.Users ?? [])
|
||||
{
|
||||
<UserRow User="user" Roles="Roles.ToList()" OnRemove="HandleOpenConfirmationPopup" OnSubmit="HandleUpdateUser" Validator="@(new UserUpdateValidator())" CanDelete=@(user.Id != currentUserId) />
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="no-users">@ResourcesKey.NoUsers</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@for (int i = 0; i < 20; i++)
|
||||
{
|
||||
<UserRowSkeleton />
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Popup @ref=Popup Closable=false>
|
||||
<ConfirmDelete OnCancel="HandleCancelPopupClicked" OnConfirm="HandleRemoveUser" />
|
||||
</Popup>
|
||||
@@ -0,0 +1,173 @@
|
||||
using GameIdeas.BlazorApp.Pages.Users.Filters;
|
||||
using GameIdeas.BlazorApp.Pages.Users.Gateways;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Popup;
|
||||
using GameIdeas.Shared.Constants;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Pages.Users;
|
||||
|
||||
public partial class Users
|
||||
{
|
||||
[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 bool IsLoading = false;
|
||||
private readonly UserFilterParams FilterParams = new();
|
||||
private UserListDto UserList = new();
|
||||
private IEnumerable<RoleDto> Roles = [];
|
||||
private readonly int CurrentPage = 1;
|
||||
private UserDto UserAdd = new();
|
||||
private UserDto? UserDelete;
|
||||
private string? currentUserId;
|
||||
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 FetchUsers();
|
||||
await FetchRoles();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private async Task FetchUsers(bool displayLoading = true)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = displayLoading;
|
||||
StateHasChanged();
|
||||
|
||||
UserList = await UserGateway.GetUsers(FilterParams, CurrentPage);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FetchRoles()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
Roles = await UserGateway.GetRoles();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleSubmitUser(UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
await UserGateway.CreateUser(user);
|
||||
await FetchUsers();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
UserAdd = new();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleUpdateUser(UserDto user)
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
await UserGateway.UpdateUser(user);
|
||||
await FetchUsers();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleRemoveUser()
|
||||
{
|
||||
Popup?.Close();
|
||||
|
||||
if (UserDelete?.Id == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
await UserGateway.DeleteUser(UserDelete.Id);
|
||||
await FetchUsers();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
UserDelete = null;
|
||||
}
|
||||
private void HandleResetUser(UserDto args)
|
||||
{
|
||||
UserAdd = new();
|
||||
}
|
||||
private async Task HandleFilterChanged()
|
||||
{
|
||||
await FetchUsers(false);
|
||||
}
|
||||
private void HandleCancelPopupClicked()
|
||||
{
|
||||
Popup?.Close();
|
||||
}
|
||||
private void HandleOpenConfirmationPopup(UserDto user)
|
||||
{
|
||||
UserDelete = user;
|
||||
Popup?.Open();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
.header-content {
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
::deep .search-container, ::deep .select-container {
|
||||
box-sizing: border-box;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20px 200px;
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
}
|
||||
|
||||
.line {
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px) {
|
||||
.container {
|
||||
padding: 20px 20px;
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
using System.Net.Http.Json;
|
||||
using Blazored.LocalStorage;
|
||||
using GameIdeas.BlazorApp;
|
||||
using GameIdeas.BlazorApp.Pages.Games.Gateways;
|
||||
using GameIdeas.BlazorApp.Pages.User.Gateways;
|
||||
using GameIdeas.BlazorApp.Pages.UserMenu.Gateways;
|
||||
using GameIdeas.BlazorApp.Pages.Users.Gateways;
|
||||
using GameIdeas.BlazorApp.Services;
|
||||
using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Constants;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
var services = builder.Services;
|
||||
@@ -15,10 +17,13 @@ var services = builder.Services;
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
|
||||
UriBuilder uriBuilder = new(builder.HostEnvironment.BaseAddress)
|
||||
{
|
||||
Port = 8000
|
||||
};
|
||||
UriBuilder uriBuilder = new(builder.HostEnvironment.BaseAddress);
|
||||
|
||||
#if DEBUG
|
||||
uriBuilder.Port = GlobalConstants.API_PORT;
|
||||
#else
|
||||
uriBuilder.Host = GlobalConstants.SUB_DOMAIN_NAME + uriBuilder.Host;
|
||||
#endif
|
||||
|
||||
services.AddHttpClient(
|
||||
"GameIdeas.WebAPI",
|
||||
@@ -37,6 +42,7 @@ services.AddScoped<IHttpClientService, HttpClientService>();
|
||||
|
||||
services.AddScoped<IAuthGateway, AuthGateway>();
|
||||
services.AddScoped<IGameGateway, GameGateway>();
|
||||
services.AddScoped<IUserGateway, UserGateway>();
|
||||
|
||||
services.AddSingleton<TranslationService>();
|
||||
services.AddSingleton<Translations>();
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "http://localhost:5172",
|
||||
"launchUrl": "http://localhost:5172/Games",
|
||||
"launchUrl": "http://localhost:5172/",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
@@ -18,7 +18,7 @@
|
||||
"launchBrowser": true,
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:7060;http://localhost:5172",
|
||||
"launchUrl": "http://localhost:7060/Games",
|
||||
"launchUrl": "http://localhost:7060/",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
using GameIdeas.Resources;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Text.Json;
|
||||
using System.Text;
|
||||
using Blazored.LocalStorage;
|
||||
using Blazored.LocalStorage;
|
||||
using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Constants;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Services;
|
||||
|
||||
public class HttpClientService(
|
||||
IHttpClientFactory httpClientFactory,
|
||||
IHttpClientFactory httpClientFactory,
|
||||
ILoggerFactory loggerFactory,
|
||||
ILocalStorageService localStorage) : IHttpClientService
|
||||
ILocalStorageService localStorage,
|
||||
AuthenticationStateProvider stateProvider) : 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,
|
||||
@@ -141,6 +143,16 @@ public class HttpClientService(
|
||||
|
||||
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);
|
||||
httpClient.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("bearer", token);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using Blazored.LocalStorage;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using System.Security.Claims;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using GameIdeas.Shared.Constants;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Services;
|
||||
|
||||
@@ -12,7 +13,7 @@ public class JwtAuthenticationStateProvider(ILocalStorageService localStorage) :
|
||||
{
|
||||
var savedToken = await localStorage.GetItemAsStringAsync(GlobalConstants.LS_AUTH_STORAGE_KEY);
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(savedToken))
|
||||
if (!string.IsNullOrWhiteSpace(savedToken))
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -31,9 +32,17 @@ public class JwtAuthenticationStateProvider(ILocalStorageService localStorage) :
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
await Js.InvokeVoidAsync("setBodyOverflow", "auto");
|
||||
await Js.InvokeVoidAsync("setBodyOverflow", "visible");
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -4,13 +4,11 @@ using GameIdeas.BlazorApp.Shared.Models;
|
||||
using GameIdeas.Resources;
|
||||
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 EventCallback<AddType> AddTypeChanged { get; set; }
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
|
||||
private readonly Dictionary<AddType, string> AddTypes = new() {
|
||||
{ AddType.Manual, ResourcesKey.ManualAdd },
|
||||
@@ -31,14 +29,9 @@ public partial class GameHeader : ComponentBase
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private void HandleIconClicked()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private async Task HandleAddTypeClicked(IEnumerable<KeyValuePair<AddType, string>> values)
|
||||
{
|
||||
SelectListAdd?.Close();
|
||||
await AddTypeChanged.InvokeAsync(values.FirstOrDefault().Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,40 +1,10 @@
|
||||
.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%;
|
||||
}
|
||||
|
||||
.account-add-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.add-buttons {
|
||||
.add-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background: var(--violet);
|
||||
border-radius: var(--small-radius);
|
||||
margin-right: 40px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.button {
|
||||
@@ -64,4 +34,10 @@
|
||||
.button-icon:hover {
|
||||
background: var(--violet-selected);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.add-buttons {
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
@@ -19,13 +19,3 @@
|
||||
height: 60px;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using GameIdeas.BlazorApp.Pages.Games.Gateways;
|
||||
using GameIdeas.BlazorApp.Shared.Models;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Shared.Components;
|
||||
|
||||
public class GameBaseComponent : ComponentBase
|
||||
{
|
||||
[Inject] protected IGameGateway GameGateway { get; set; } = default!;
|
||||
|
||||
protected Popup.Popup? ManualAddPopup;
|
||||
protected CategoriesDto? Categories;
|
||||
protected bool IsLoading = false;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await HandleFetchCategories();
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected async Task HandleFetchCategories()
|
||||
{
|
||||
try
|
||||
{
|
||||
IsLoading = true;
|
||||
StateHasChanged();
|
||||
|
||||
Categories = await GameGateway.FetchCategories();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
protected void HandleAddClicked(AddType addType)
|
||||
{
|
||||
switch (addType)
|
||||
{
|
||||
case AddType.Manual:
|
||||
ManualAddPopup?.Open();
|
||||
break;
|
||||
case AddType.Auto:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void HandleBackdropManualAddClicked()
|
||||
{
|
||||
ManualAddPopup?.Close();
|
||||
}
|
||||
}
|
||||
@@ -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,8 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Shared.Components.Header;
|
||||
|
||||
public partial class HeaderGameIdeas : ComponentBase
|
||||
{
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
.header-tab {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-right: 10px;
|
||||
|
||||
}
|
||||
|
||||
.icon-container img {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
@using GameIdeas.BlazorApp.Helpers
|
||||
|
||||
<div class="interest">
|
||||
<span class="value" style="@($"color: var({GameHelper.GetInterestColor(Value, 5)})")">
|
||||
@Value
|
||||
</span>
|
||||
<span class="max-value">/5</span>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Shared.Components.Interest;
|
||||
|
||||
public partial class Interest
|
||||
{
|
||||
[Parameter] public int Value { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
.interest {
|
||||
position: relative;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.value {
|
||||
align-content: center;
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.max-value {
|
||||
align-content: center;
|
||||
transform: translate(2px, 10px);
|
||||
color: rgb(184, 184, 184);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="confirm-section">
|
||||
<span class="descrption">@ResourcesKey.ConfirmDeleteDescription</span>
|
||||
<div class="buttons">
|
||||
<div class="cancel" @onclick=HandleCancelClicked>@ResourcesKey.Cancel</div>
|
||||
<div class="confirm" @onclick=HandleConfirmClicked>@ResourcesKey.Confirm</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Shared.Components.Popup.Components;
|
||||
|
||||
public partial class ConfirmDelete
|
||||
{
|
||||
[Parameter] public EventCallback OnCancel { get; set; }
|
||||
[Parameter] public EventCallback OnConfirm { get; set; }
|
||||
|
||||
private async Task HandleConfirmClicked()
|
||||
{
|
||||
await OnConfirm.InvokeAsync();
|
||||
}
|
||||
private async Task HandleCancelClicked()
|
||||
{
|
||||
await OnCancel.InvokeAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
.confirm-section {
|
||||
padding: 10px 20px;
|
||||
display: grid;
|
||||
grid-gap: 20px;
|
||||
}
|
||||
|
||||
.descrption {
|
||||
|
||||
}
|
||||
|
||||
.buttons {
|
||||
justify-content: end;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
|
||||
.cancel, .confirm {
|
||||
height: 28px;
|
||||
align-content: center;
|
||||
padding: 0 10px;
|
||||
background: var(--violet);
|
||||
border-radius: var(--small-radius);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cancel:hover, .confirm:hover {
|
||||
background: var(--violet-selected);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<div class="readmore-container">
|
||||
<div class="text">
|
||||
@DisplayedText()
|
||||
</div>
|
||||
@if (Text?.Length > MaxLength && !_showFullText)
|
||||
{
|
||||
<div class="fade-overlay"></div>
|
||||
}
|
||||
@if (Text?.Length > MaxLength)
|
||||
{
|
||||
<button type="button" class="button" @onclick=ToggleFullText>
|
||||
@(_showFullText ? @ResourcesKey.ReadLess : @ResourcesKey.ReadMore)
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Shared.Components.ReadMore;
|
||||
|
||||
public partial class ReadMore
|
||||
{
|
||||
private bool _showFullText = false;
|
||||
|
||||
[Parameter]
|
||||
public string? Text { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int MaxLength { get; set; } = 100;
|
||||
|
||||
private string DisplayedText()
|
||||
{
|
||||
if (Text == null) return string.Empty;
|
||||
if (_showFullText || Text.Length <= MaxLength) return Text;
|
||||
|
||||
return Text[..MaxLength] + "...";
|
||||
}
|
||||
|
||||
private void ToggleFullText() => _showFullText = !_showFullText;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
.button {
|
||||
margin: 0 auto 10px auto;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: var(--violet);
|
||||
border-radius: var(--small-radius);
|
||||
z-index: var(--index-floating);
|
||||
color: var(--white);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
padding: 2px 8px;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
background: var(--violet-selected);
|
||||
}
|
||||
|
||||
.text {
|
||||
overflow-wrap: break-word;
|
||||
margin: 0 10px;
|
||||
}
|
||||
|
||||
.readmore-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
color: var(--white);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
white-space: break-spaces;
|
||||
}
|
||||
|
||||
.fade-overlay {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4.5em;
|
||||
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.4));
|
||||
border-radius: 0 0 var(--big-radius) var(--big-radius);
|
||||
pointer-events: none;
|
||||
}
|
||||
@@ -2,12 +2,12 @@
|
||||
@using GameIdeas.Shared.Constants
|
||||
|
||||
<div class="search-container">
|
||||
<input id="searchInput"
|
||||
type="text"
|
||||
<input type="text"
|
||||
class="search-field"
|
||||
placeholder="@Placeholder"
|
||||
disabled="@IsDisable"
|
||||
style="@(IsDisable ? "pointer-events: none" : "")"
|
||||
onClick="this.select();"
|
||||
@bind=@Text
|
||||
@bind:event="oninput"
|
||||
@bind:after="HandleTextChanged"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using GameIdeas.BlazorApp.Shared.Constants;
|
||||
using GameIdeas.Shared.Constants;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Shared.Components.Search;
|
||||
@@ -21,7 +22,7 @@ public partial class SearchInput
|
||||
Text = string.Empty;
|
||||
Timer = new()
|
||||
{
|
||||
Interval = 500,
|
||||
Interval = GlobalConstants.DELAY_INPUT_MS,
|
||||
AutoReset = false,
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
min-width: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.clear-icon {
|
||||
@@ -34,6 +34,7 @@
|
||||
min-width: 18px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
z-index: 800;
|
||||
}
|
||||
|
||||
.clear-icon:hover {
|
||||
|
||||
@@ -42,4 +42,30 @@
|
||||
|
||||
.navigation .selected {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/***** Single Theme *****/
|
||||
.single {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.single .selected {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/***** Navigation Theme *****/
|
||||
.row-option {
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.row-option:hover {
|
||||
background: var(--violet-selected);
|
||||
}
|
||||
|
||||
.row-option .selected {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.row-option:last-child {
|
||||
color: var(--red);
|
||||
}
|
||||
@@ -13,6 +13,8 @@ public static class SelectHelper
|
||||
SelectTheme.Filter => "filter",
|
||||
SelectTheme.AdvancedFilter => "advanced-filter",
|
||||
SelectTheme.Creation => "creation",
|
||||
SelectTheme.Single => "single",
|
||||
SelectTheme.RowOption => "row-option",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,8 +4,10 @@ public class SelectParams<TItem, THeader>
|
||||
{
|
||||
public List<TItem> Items { get; set; } = [];
|
||||
public Func<TItem, string> GetItemLabel { get; set; } = _ => string.Empty;
|
||||
public Func<TItem, string>? GetItemOrder { get; set; }
|
||||
public List<THeader> Headers { get; set; } = [];
|
||||
public Func<THeader, string> GetHeaderLabel { get; set; } = _ => string.Empty;
|
||||
public Func<THeader, string>? GetHeaderOrder { get; set; }
|
||||
public Func<string, TItem>? AddItem { get; set; }
|
||||
|
||||
}
|
||||
|
||||
@@ -6,5 +6,7 @@ public enum SelectTheme
|
||||
Sort,
|
||||
Filter,
|
||||
AdvancedFilter,
|
||||
Creation
|
||||
Creation,
|
||||
Single,
|
||||
RowOption
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
@typeparam THeader
|
||||
|
||||
<div class="select-container">
|
||||
<div class="button" @onclick=HandleButtonClicked>
|
||||
<div class="button @(IsContentOpen ? "selected" : "")" @onclick=HandleButtonClicked>
|
||||
@ChildContent
|
||||
</div>
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
|
||||
@if (Params.Headers != null)
|
||||
{
|
||||
@foreach (var header in Params.Headers.Union(HeaderValues ?? []))
|
||||
@foreach (var header in GetHeaders())
|
||||
{
|
||||
<SelectRow IsSelected=HeaderValues?.Contains(header)
|
||||
<SelectRow IsSelected=@(HeaderValues?.Contains(header))
|
||||
Label="@Params.GetHeaderLabel(header)" Theme=Theme
|
||||
OnClick="_ => HandleHeaderClicked(header)" />
|
||||
}
|
||||
@@ -42,9 +42,9 @@
|
||||
|
||||
@if (Params.Items != null)
|
||||
{
|
||||
@foreach (var item in Params.Items.Union(Values ?? []))
|
||||
@foreach (var item in GetItems())
|
||||
{
|
||||
<SelectRow IsSelected=Values?.Contains(item)
|
||||
<SelectRow IsSelected=@(Values?.Contains(item))
|
||||
Label="@Params.GetItemLabel(item)" Theme=Theme
|
||||
OnClick="_ => HandleValueClicked(item)" />
|
||||
}
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
|
||||
using GameIdeas.Resources;
|
||||
using GameIdeas.Shared.Constants;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Shared.Components.Select;
|
||||
|
||||
@@ -92,11 +89,24 @@ public partial class Select<TItem, THeader>
|
||||
if (Params.AddItem != null)
|
||||
{
|
||||
Values ??= [];
|
||||
Values.Add(Params.AddItem(AddLabel));
|
||||
|
||||
if (Type != SelectType.Multiple)
|
||||
{
|
||||
Values = [];
|
||||
}
|
||||
|
||||
Values.Add(Params.AddItem(AddLabel));
|
||||
AddLabel = string.Empty;
|
||||
|
||||
await ValuesChanged.InvokeAsync(Values);
|
||||
}
|
||||
}
|
||||
|
||||
private List<THeader> GetHeaders() => [.. (HeaderValues ?? [])
|
||||
.UnionBy(Params.Headers, Params.GetHeaderLabel)
|
||||
.OrderBy(Params.GetHeaderOrder ?? Params.GetHeaderLabel)];
|
||||
|
||||
private List<TItem> GetItems() => [.. (Values ?? [])
|
||||
.UnionBy(Params.Items, Params.GetItemLabel)
|
||||
.OrderBy(Params.GetItemOrder ?? Params.GetItemLabel)];
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
z-index: var(--index-dropdown);
|
||||
border-radius: var(--small-radius);
|
||||
width: 100%;
|
||||
box-shadow: var(--drop-shadow);
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -21,9 +22,6 @@
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation-name: fade-in;
|
||||
animation-duration: 0.2s;
|
||||
box-shadow: var(--drop-shadow);
|
||||
}
|
||||
|
||||
.line {
|
||||
@@ -87,7 +85,7 @@
|
||||
border-bottom: 2px solid var(--input-selected);
|
||||
}
|
||||
|
||||
/***** Sort Theme *****/
|
||||
/***** Creation Theme *****/
|
||||
.creation .content {
|
||||
border-radius: var(--small-radius);
|
||||
box-sizing: border-box;
|
||||
@@ -100,3 +98,17 @@
|
||||
border-bottom: 2px solid var(--input-selected);
|
||||
}
|
||||
|
||||
/***** Single Theme *****/
|
||||
.single {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/***** Row Option Theme *****/
|
||||
.dropdown.row-option {
|
||||
width: auto;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.row-option .content {
|
||||
background: var(--violet);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
|
||||
@typeparam TItem
|
||||
|
||||
<Select @ref=Select TItem="TItem" THeader="string" Theme="Theme" Type="SelectType.Multiple"
|
||||
<Select @ref=Select TItem="TItem" THeader="string" Theme="Theme" Type="SelectType" DisableClicked=true
|
||||
Params="SelectParams" Values=Values ValuesChanged="HandleValuesChanged" QuickAdd=QuickAdd>
|
||||
|
||||
<div class="@SelectHelper.GetClassFromTheme(Theme)">
|
||||
<SearchInput @ref=SearchInput Icon="SearchInputIcon.Dropdown" Placeholder="@Placeholder"
|
||||
TextChanged="HandleClearClicked" ClearClicked="HandleClearClicked" IsDisable=QuickAdd
|
||||
TextChanged="HandleTextChanged" ClearClicked="HandleClearClicked" IsDisable=false
|
||||
FocusIn="HandleFocusIn" SearchClicked="HandleFocusIn" />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using GameIdeas.BlazorApp.Pages.Games;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Search;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select;
|
||||
using GameIdeas.BlazorApp.Shared.Components.Select.Models;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Shared.Components.SelectSearch;
|
||||
@@ -10,11 +11,13 @@ public partial class SelectSearch<TItem>
|
||||
[Parameter] public SelectTheme Theme { get; set; }
|
||||
[Parameter] public List<TItem> Items { get; set; } = [];
|
||||
[Parameter] public Func<TItem, string> GetLabel { get; set; } = _ => string.Empty;
|
||||
[Parameter] public Func<TItem, string>? OrderBy { get; set; }
|
||||
[Parameter] public List<TItem> Values { get; set; } = [];
|
||||
[Parameter] public EventCallback<List<TItem>> ValuesChanged { get; set; }
|
||||
[Parameter] public string Placeholder { get; set; } = string.Empty;
|
||||
[Parameter] public bool QuickAdd { get; set; } = false;
|
||||
[Parameter] public Func<string, TItem>? AddItem { get; set; }
|
||||
[Parameter] public SelectType SelectType { get; set; } = SelectType.Multiple;
|
||||
|
||||
private SelectParams<TItem, string> SelectParams = new();
|
||||
private SearchInput? SearchInput;
|
||||
@@ -26,26 +29,62 @@ public partial class SelectSearch<TItem>
|
||||
{
|
||||
Items = Items,
|
||||
GetItemLabel = GetLabel,
|
||||
GetItemOrder = OrderBy,
|
||||
AddItem = AddItem
|
||||
};
|
||||
|
||||
base.OnParametersSet();
|
||||
}
|
||||
|
||||
protected override void OnAfterRender(bool firstRender)
|
||||
{
|
||||
if (firstRender && Values != null)
|
||||
{
|
||||
SearchInput?.SetText(string.Join(", ", Values.Select(GetLabel)));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleValuesChanged(IEnumerable<TItem> values)
|
||||
{
|
||||
Values = values.ToList();
|
||||
Values = [.. values];
|
||||
SearchInput?.SetText(string.Join(", ", Values.Select(GetLabel)));
|
||||
await ValuesChanged.InvokeAsync(Values.ToList());
|
||||
await ValuesChanged.InvokeAsync([.. Values]);
|
||||
}
|
||||
|
||||
private async Task HandleClearClicked()
|
||||
{
|
||||
Values = [];
|
||||
await ValuesChanged.InvokeAsync(Values.ToList());
|
||||
Select?.Close();
|
||||
SearchInput?.SetText(string.Empty);
|
||||
await ValuesChanged.InvokeAsync([]);
|
||||
}
|
||||
|
||||
private void HandleFocusIn()
|
||||
{
|
||||
Select?.Open();
|
||||
}
|
||||
private void HandleTextChanged(string args)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(args))
|
||||
{
|
||||
var keywords = args
|
||||
.Split([' '], StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(k => k.Trim())
|
||||
.ToArray() ?? [];
|
||||
|
||||
SelectParams.Items = [.. Items
|
||||
.Where(game => keywords.All(
|
||||
kw => SelectParams.GetItemLabel(game).Contains(kw, StringComparison.OrdinalIgnoreCase)
|
||||
))
|
||||
.OrderBy(game => keywords.Min(kw =>
|
||||
SelectParams.GetItemLabel(game).IndexOf(kw, StringComparison.OrdinalIgnoreCase)
|
||||
))
|
||||
.ThenBy(game => SelectParams.GetItemLabel(game).Length)];
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectParams.Items = Items;
|
||||
SearchInput?.SetText(string.Join(", ", Values?.Select(GetLabel) ?? []));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
<div class="container">
|
||||
<div class="slider-track"></div>
|
||||
|
||||
<input type="range" id="min-range" style="@StatusColor(Value)" min="@Params.Min" max="@Params.Max"
|
||||
@bind="@Value" @bind:event="oninput" @bind:after=HandleSlideOnInput />
|
||||
<div class="input">
|
||||
<div class="slider-track"></div>
|
||||
|
||||
<div class="values">
|
||||
<span class="value">@Params.Min</span>
|
||||
<span class="value">@Params.Max</span>
|
||||
<input type="range" id="min-range" style="@StatusColor(Value)" min="@Params.Min" max="@Params.Max"
|
||||
@bind="@Value" @bind:event="oninput" @bind:after=HandleSlideOnInput />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span class="value">@Value</span>
|
||||
</div>
|
||||
@@ -1,7 +1,7 @@
|
||||
.container {
|
||||
position: relative;
|
||||
.input {
|
||||
width: 100%;
|
||||
z-index: 0
|
||||
position: relative;
|
||||
height: 2px;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
@@ -24,7 +24,6 @@ input[type="range"] {
|
||||
margin: auto;
|
||||
border-radius: 2px;
|
||||
background: var(--input-primary);
|
||||
|
||||
}
|
||||
|
||||
input[type="range"]::-webkit-slider-runnable-track {
|
||||
@@ -75,15 +74,13 @@ input[type="range"]::-ms-thumb {
|
||||
}
|
||||
|
||||
.values {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
margin-top: 2px;
|
||||
width: 100%;
|
||||
font-weight: bold;
|
||||
justify-content: space-between;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.value {
|
||||
width: 1em;
|
||||
text-align:center;
|
||||
.container {
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
public class SliderParams
|
||||
{
|
||||
public int Min{ get; set; }
|
||||
public int Min { get; set; }
|
||||
public int Max { get; set; }
|
||||
public int Gap { get; set; } = 0;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<div class="container">
|
||||
<div class="slider-track" style="@FillColor()"></div>
|
||||
|
||||
<input type="range" id="min-range" style="@StatusColor(Min)" min="@Params.Min" max="@Params.Max"
|
||||
@bind="@Min" @bind:event="oninput" @bind:after=HandleSlideOnInput />
|
||||
<input type="range" id="max-range" style="@StatusColor(Max)" min="@Params.Min" max="@Params.Max"
|
||||
@bind="@Max" @bind:event="oninput" @bind:after=HandleSlideTwoInput />
|
||||
<input type="range" id="min-range" style="@StatusColor(Value.Min)" min="@Params.Min" max="@Params.Max"
|
||||
@bind="@Value.Min" @bind:event="oninput" @bind:after=HandleSlideOnInput />
|
||||
<input type="range" id="max-range" style="@StatusColor(Value.Max)" min="@Params.Min" max="@Params.Max"
|
||||
@bind="@Value.Max" @bind:event="oninput" @bind:after=HandleSlideTwoInput />
|
||||
|
||||
<div class="values">
|
||||
<span class="value">@Min</span>
|
||||
<span class="value">@Max</span>
|
||||
<span class="value">@Value.Min</span>
|
||||
<span class="value">@Value.Max</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,44 +1,58 @@
|
||||
using GameIdeas.Shared.Constants;
|
||||
using GameIdeas.Shared.Dto;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GameIdeas.BlazorApp.Shared.Components.SliderRange;
|
||||
|
||||
public partial class SliderRange
|
||||
{
|
||||
[Parameter] public SliderRangeParams Params { get; set; } = new();
|
||||
[Parameter] public int Max { get; set; }
|
||||
[Parameter] public EventCallback<int> MaxChanged { get; set; }
|
||||
[Parameter] public int Min { get; set; }
|
||||
[Parameter] public EventCallback<int> MinChanged { get; set; }
|
||||
[Parameter] public MinMaxDto Value { get; set; } = new() { Min = 1, Max = 5 };
|
||||
[Parameter] public EventCallback<MinMaxDto> ValueChanged { 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,
|
||||
};
|
||||
|
||||
await MaxChanged.InvokeAsync(Max);
|
||||
Timer.Elapsed += async (_, _) => await ValueChanged.InvokeAsync(Value);
|
||||
base.OnInitialized();
|
||||
}
|
||||
|
||||
private async Task HandleSlideOnInput()
|
||||
private void HandleSlideTwoInput()
|
||||
{
|
||||
if (Max - Min <= Params.Gap)
|
||||
if (Value.Max - Value.Min <= Params.Gap)
|
||||
{
|
||||
Max = Min + Params.Gap;
|
||||
Value.Min = Value.Max - Params.Gap;
|
||||
}
|
||||
|
||||
await MinChanged.InvokeAsync(Min);
|
||||
Timer?.Stop();
|
||||
Timer?.Start();
|
||||
}
|
||||
|
||||
private void HandleSlideOnInput()
|
||||
{
|
||||
if (Value.Max - Value.Min <= Params.Gap)
|
||||
{
|
||||
Value.Max = Value.Min + Params.Gap;
|
||||
}
|
||||
|
||||
Timer?.Stop();
|
||||
Timer?.Start();
|
||||
}
|
||||
|
||||
private string FillColor()
|
||||
{
|
||||
var percent1 = (double)(Min - Params.Min) / (Params.Max - Params.Min) * 100;
|
||||
var percent2 = (double)(Max - Params.Min) / (Params.Max - Params.Min) * 100;
|
||||
var percent1 = (double?)(Value.Min - 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}%)";
|
||||
}
|
||||
|
||||
private string StatusColor(int value)
|
||||
private string StatusColor(int? value)
|
||||
{
|
||||
string str = "--thumb-color: var({0});";
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
public class SliderRangeParams
|
||||
{
|
||||
public int Min{ get; set; }
|
||||
public int Min { get; set; }
|
||||
public int Max { get; set; }
|
||||
public int Gap { get; set; } = 0;
|
||||
}
|
||||
|
||||
@@ -7,17 +7,30 @@ public static class Endpoints
|
||||
{
|
||||
public static class Game
|
||||
{
|
||||
public static readonly string Create = "api/Game/Create";
|
||||
public const string Create = "api/Game/Create";
|
||||
public static string Fetch(GameFilterDto filter) => $"api/Game?{UrlHelper.BuildUrlParams(filter)}";
|
||||
public static string FetchById(int gameId) => $"api/Game/{gameId}";
|
||||
public static string Delete(int gameId) => $"api/Game/Delete/{gameId}";
|
||||
public const string Update = "api/Game/Update";
|
||||
}
|
||||
|
||||
public static class Category
|
||||
{
|
||||
public static readonly string AllCategories = "api/Category/All";
|
||||
public const string AllCategories = "api/Category/All";
|
||||
}
|
||||
|
||||
public static class Auth
|
||||
{
|
||||
public static readonly string Login = "api/User/Login";
|
||||
public const string Login = "api/User/Login";
|
||||
}
|
||||
|
||||
public static class User
|
||||
{
|
||||
public static string Fetch(UserFilterDto filter) => $"api/User?{UrlHelper.BuildUrlParams(filter)}";
|
||||
public const string Roles = "api/User/Roles";
|
||||
public const string Create = "api/User/Create";
|
||||
public static string Delete(string userId) => $"api/User/Delete/{userId}";
|
||||
public static string Update(string userId) => $"api/User/Update/{userId}";
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public static class Icons
|
||||
public readonly static MarkupString Triangle = new(OpenBraket +
|
||||
"<path d=\"M1 3H23L12 22\" />" +
|
||||
CloseBraket);
|
||||
|
||||
|
||||
public readonly static MarkupString Close = new(OpenBraket +
|
||||
"<path d=\"M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z\" />" +
|
||||
CloseBraket);
|
||||
@@ -27,4 +27,24 @@ public static class Icons
|
||||
public readonly static MarkupString Account = new(OpenBraket +
|
||||
"<path d=\"M12,19.2C9.5,19.2 7.29,17.92 6,16C6.03,14 10,12.9 12,12.9C14,12.9 17.97,14 18,16C16.71,17.92 14.5,19.2 12,19.2M12,5A3,3 0 0,1 15,8A3,3 0 0,1 12,11A3,3 0 0,1 9,8A3,3 0 0,1 12,5M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12C22,6.47 17.5,2 12,2Z\" />" +
|
||||
CloseBraket);
|
||||
|
||||
public readonly static MarkupString Bin = new(OpenBraket +
|
||||
"<path d=\"M19,4H15.5L14.5,3H9.5L8.5,4H5V6H19M6,19A2,2 0 0,0 8,21H16A2,2 0 0,0 18,19V7H6V19Z\" />" +
|
||||
CloseBraket);
|
||||
|
||||
public readonly static MarkupString Pen = new(OpenBraket +
|
||||
"<path d=\"M20.71,7.04C21.1,6.65 21.1,6 20.71,5.63L18.37,3.29C18,2.9 17.35,2.9 16.96,3.29L15.12,5.12L18.87,8.87M3,17.25V21H6.75L17.81,9.93L14.06,6.18L3,17.25Z\" />" +
|
||||
CloseBraket);
|
||||
|
||||
public readonly static MarkupString Check = new(OpenBraket +
|
||||
"<path d=\"M9,20.42L2.79,14.21L5.62,11.38L9,14.77L18.88,4.88L21.71,7.71L9,20.42Z\" />" +
|
||||
CloseBraket);
|
||||
|
||||
public readonly static MarkupString Back = new(OpenBraket +
|
||||
"<path d=\"M20,11V13H8L13.5,18.5L12.08,19.92L4.16,12L12.08,4.08L13.5,5.5L8,11H20Z\" />" +
|
||||
CloseBraket);
|
||||
|
||||
public readonly static MarkupString Filter = new(OpenBraket +
|
||||
"<path d=\"M15,19.88C15.04,20.18 14.94,20.5 14.71,20.71C14.32,21.1 13.69,21.1 13.3,20.71L9.29,16.7C9.06,16.47 8.96,16.16 9,15.87V10.75L4.21,4.62C3.87,4.19 3.95,3.56 4.38,3.22C4.57,3.08 4.78,3 5,3V3H19V3C19.22,3 19.43,3.08 19.62,3.22C20.05,3.56 20.13,4.19 19.79,4.62L15,10.75V19.88M7.04,5L11,10.06V15.58L13,17.58V10.05L16.96,5H7.04Z\" />" +
|
||||
CloseBraket);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace GameIdeas.BlazorApp.Shared.Exceptions;
|
||||
|
||||
public class FetchGameDetailException(string message) : Exception(message);
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace GameIdeas.BlazorApp.Shared.Exceptions;
|
||||
|
||||
public class GameDeletionException(string message) : Exception(message);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user