feat : add authorize , add brands page

config authorize and ready for create login page , add brands page and brand action dialog
release
Amir Hossein Khademi 2024-01-23 09:13:40 +03:30
parent 493042812d
commit a8af53c9d9
18 changed files with 346 additions and 61 deletions

View File

@ -1,7 +1,15 @@
<Router AppAssembly="@typeof(App).Assembly"> @using NetinaShop.AdminPanel.PWA.Pages
@inject NavigationManager NavigationManager
<Router AppAssembly="@typeof(App).Assembly">
<Found Context="routeData"> <Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" /> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<LoginPage/>
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/> <FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found> </Found>
<NotFound> <NotFound>
<PageTitle>Not found</PageTitle> <PageTitle>Not found</PageTitle>
@ -9,4 +17,6 @@
<p role="alert">Sorry, there's nothing at this address.</p> <p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView> </LayoutView>
</NotFound> </NotFound>
</Router> </Router>

View File

@ -0,0 +1,5 @@
<h3>BrandActionDialogBox</h3>
@code {
}

View File

@ -1,6 +1,9 @@
@using Blazorise.Extensions @using NetinaShop.Common.Models.Exception
@using NetinaShop.Domain.CommandQueries.Commands
@inject ISnackbar Snackbar @inject ISnackbar Snackbar
@inject IRestWrapper RestWrapper @inject IRestWrapper RestWrapper
@inject IUserUtility UserUtility
<MudDialog class="mx-auto"> <MudDialog class="mx-auto">
<DialogContent> <DialogContent>
@ -63,6 +66,32 @@
void Submit() => MudDialog.Close(DialogResult.Ok(true)); void Submit() => MudDialog.Close(DialogResult.Ok(true));
void Cancel() => MudDialog.Cancel(); void Cancel() => MudDialog.Cancel();
private string _name;
private string _description;
private bool _isMain;
private async Task SubmitCreateAsync()
{
try
{
if (_name.IsNullOrEmpty())
throw new AppException("لطفا نام دسته را وارد کنید");
var token = await UserUtility.GetBearerTokenAsync();
var request = new ProductCreateCategoryCommand(_name, _description, _selectedCategory?.Id ?? default, new List<StorageFileSDto>());
await RestWrapper.CrudApiRest<ProductCategory, Guid>(Address.ProductCategoryController).Create<ProductCreateCategoryCommand>(request, token);
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
if (exe != null)
Snackbar.Add(exe.Message, Severity.Error);
Snackbar.Add(ex.Content, Severity.Error);
}
catch (Exception e)
{
Snackbar.Add(e.Message, Severity.Error);
}
}
private List<ProductCategorySDto> _productCategories = new List<ProductCategorySDto>(); private List<ProductCategorySDto> _productCategories = new List<ProductCategorySDto>();
private ProductCategorySDto? _selectedCategory; private ProductCategorySDto? _selectedCategory;

View File

@ -22,6 +22,10 @@
color: #fff !important; color: #fff !important;
} }
</style> </style>
<AuthorizeView>
<Authorized>
<MudRTLProvider RightToLeft="true"> <MudRTLProvider RightToLeft="true">
<MudThemeProvider Theme="MyCustomTheme" /> <MudThemeProvider Theme="MyCustomTheme" />
<MudDialogProvider /> <MudDialogProvider />
@ -63,6 +67,26 @@
</MudGrid> </MudGrid>
</MudLayout> </MudLayout>
</MudRTLProvider> </MudRTLProvider>
</Authorized>
<NotAuthorized>
<MudRTLProvider RightToLeft="true">
<MudThemeProvider Theme="MyCustomTheme" />
<MudDialogProvider />
<MudSnackbarProvider />
<MudLayout>
<div>
@Body
<div dir="ltr">
<PWAUpdater Text="@_updateText" ButtonCaption="اپدیت کنید"/>
</div>
</div>
</MudLayout>
</MudRTLProvider>
</NotAuthorized>
</AuthorizeView>
@code @code
{ {
MudTheme MyCustomTheme = new MudTheme() MudTheme MyCustomTheme = new MudTheme()

View File

@ -11,4 +11,5 @@ public static class Address
public static string AuthController = $"{BaseAddress}/auth"; public static string AuthController = $"{BaseAddress}/auth";
public static string UserController = $"{BaseAddress}/user"; public static string UserController = $"{BaseAddress}/user";
public static string ProductCategoryController = $"{BaseAddress}/product/category"; public static string ProductCategoryController = $"{BaseAddress}/product/category";
public static string BrandController = $"{BaseAddress}/brand";
} }

View File

@ -14,11 +14,13 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Blazored.LocalStorage" Version="4.4.0" /> <PackageReference Include="Blazored.LocalStorage" Version="4.4.0" />
<PackageReference Include="Blazorise.LottieAnimation" Version="1.4.0" /> <PackageReference Include="Blazorise.LottieAnimation" Version="1.4.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1" />
<PackageReference Include="MudBlazor" Version="6.11.2" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.1" PrivateAssets="all" />
<PackageReference Include="MudBlazor" Version="6.13.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Radzen.Blazor" Version="4.24.1" /> <PackageReference Include="Radzen.Blazor" Version="4.24.2" />
<PackageReference Include="Refit" Version="7.0.0" /> <PackageReference Include="Refit" Version="7.0.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="7.0.0" /> <PackageReference Include="Refit.HttpClientFactory" Version="7.0.0" />
<PackageReference Include="Refit.Newtonsoft.Json" Version="7.0.0" /> <PackageReference Include="Refit.Newtonsoft.Json" Version="7.0.0" />
@ -40,10 +42,19 @@
<ItemGroup> <ItemGroup>
<Using Include="Blazored.LocalStorage" /> <Using Include="Blazored.LocalStorage" />
<Using Include="Microsoft.AspNetCore.Components" />
<Using Include="Microsoft.JSInterop" />
<Using Include="MudBlazor" />
<Using Include="NetinaShop.AdminPanel.PWA.Dialogs" />
<Using Include="NetinaShop.AdminPanel.PWA.Dialogs.Originals" />
<Using Include="NetinaShop.AdminPanel.PWA.Models" /> <Using Include="NetinaShop.AdminPanel.PWA.Models" />
<Using Include="NetinaShop.AdminPanel.PWA.Models.Api" />
<Using Include="NetinaShop.AdminPanel.PWA.Services.RestServices" />
<Using Include="NetinaShop.AdminPanel.PWA.Utilities" />
<Using Include="NetinaShop.Common.Models.Api" /> <Using Include="NetinaShop.Common.Models.Api" />
<Using Include="NetinaShop.Domain.Dtos.RequestDtos" /> <Using Include="NetinaShop.Domain.Dtos.RequestDtos" />
<Using Include="NetinaShop.Domain.Dtos.SmallDtos" /> <Using Include="NetinaShop.Domain.Dtos.SmallDtos" />
<Using Include="NetinaShop.Domain.Entities.ProductCategories" />
<Using Include="NetinaShop.Domain.Enums" /> <Using Include="NetinaShop.Domain.Enums" />
<Using Include="Newtonsoft.Json" /> <Using Include="Newtonsoft.Json" />
<Using Include="Refit" /> <Using Include="Refit" />

View File

@ -1,6 +1,80 @@
@page "/BrandsPage" @page "/BrandsPage"
<h3>BrandsPage</h3> @inject IDialogService DialogService
@inject NavigationManager NavigationManager
@inject IRestWrapper RestWrapper
@inject ISnackbar Snackbar
@inject IUserUtility UserUtility
<MudStack class="w-full p-8 h-screen bg-[--color-background]">
<MudGrid>
<MudItem xs="12">
<MudStack Row="true" class="mb-5">
<MudText Typo="Typo.h4">برنــــدها</MudText>
<MudChip Color="Color.Info" Variant="Variant.Outlined">124 عدد</MudChip>
<MudSpacer />
<MudButton Variant="Variant.Filled"
DisableElevation="true"
StartIcon="@Icons.Material.Outlined.Add"
Color="Color.Secondary"
OnClick="ViewModel.AddBrandClicked"
class="my-auto">افزودن برند</MudButton>
</MudStack>
<MudPaper>
<MudDataGrid Striped="true" T="BrandSDto" Items="@ViewModel.PageDto" Filterable="false" Loading="@ViewModel.IsProcessing"
SortMode="@SortMode.None" Groupable="false">
<ToolBarContent>
<MudTextField T="string" Placeholder="جست جو بر اساس نام" Adornment="Adornment.Start" Immediate="true"
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" class="my-auto"></MudTextField>
</ToolBarContent>
<Columns>
<PropertyColumn Title="نام برند" Property="arg => arg.Name" />
<PropertyColumn Title="توضیحاتــ" Property="arg => arg.Description" />
<TemplateColumn Title="صفحه اختصاصی دارد" T="BrandSDto">
<CellTemplate>
@if (@context.Item.HasSpecialPage)
{
<p>بلی</p>
}
else
{
<p>خیر</p>
}
</CellTemplate>
</TemplateColumn>
<TemplateColumn CellClass="d-flex justify-end">
<CellTemplate>
<MudStack Row="true">
<MudIconButton Icon="@Icons.Material.Filled.Edit" Size="@Size.Small" Variant="@Variant.Outlined" Color="@Color.Info"></MudIconButton>
<MudIconButton Icon="@Icons.Material.Filled.Delete"
Size="@Size.Small"
Variant="@Variant.Outlined"
OnClick="async() => await ViewModel.DeleteBrandAsync(context.Item.Id)"
Color="@Color.Error"></MudIconButton>
</MudStack>
</CellTemplate>
</TemplateColumn>
</Columns>
</MudDataGrid>
</MudPaper>
</MudItem>
</MudGrid>
</MudStack>
@code
{
public BrandsPageViewModel ViewModel { get; set; }
protected override async Task OnInitializedAsync()
{
ViewModel = new BrandsPageViewModel(NavigationManager, Snackbar, UserUtility, RestWrapper, DialogService);
await ViewModel.InitializeAsync();
await base.OnInitializedAsync();
}
}
@code { @code {

View File

@ -0,0 +1,90 @@
using NetinaShop.Domain.Entities.Brands;
namespace NetinaShop.AdminPanel.PWA.Pages;
public class BrandsPageViewModel : BaseViewModel<List<BrandSDto>>
{
private readonly NavigationManager _navigationManager;
private readonly ISnackbar _snackbar;
private readonly IUserUtility _userUtility;
private readonly IDialogService _dialogService;
private readonly IRestWrapper _restWrapper;
public BrandsPageViewModel(NavigationManager navigationManager, ISnackbar snackbar, IUserUtility userUtility, IRestWrapper restWrapper, IDialogService dialogService)
{
_navigationManager = navigationManager;
_snackbar = snackbar;
_userUtility = userUtility;
_restWrapper = restWrapper;
_dialogService = dialogService;
}
public override async Task InitializeAsync()
{
try
{
IsProcessing = true;
var dto = await _restWrapper.CrudDtoApiRest<Brand, BrandSDto, Guid>(Address.BrandController)
.ReadAll(0);
PageDto = dto;
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
_snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
finally
{
IsProcessing = false;
}
await base.InitializeAsync();
}
public async Task AddBrandClicked()
{
DialogOptions maxWidth = new DialogOptions() { MaxWidth = MaxWidth.Medium, FullWidth = true, DisableBackdropClick = true };
await _dialogService.ShowAsync<BrandActionDialogBox>("افزودن برند جدید", maxWidth);
}
public async Task DeleteBrandAsync(Guid selectedCategoryId)
{
var options = new DialogOptions { CloseOnEscapeKey = true };
var parameters = new DialogParameters<QuestionDialog>();
parameters.Add(x => x.ContentText, "آیا از حذف برند اطمینان دارید ?");
var dialogReference = await _dialogService.ShowAsync<QuestionDialog>("حذف برند", parameters, options);
var result = await dialogReference.Result;
if (!result.Canceled)
{
try
{
IsProcessing = true;
var token = await _userUtility.GetBearerTokenAsync();
await _restWrapper.CrudDtoApiRest<Brand, BrandSDto, Guid>(Address.BrandController)
.Delete(selectedCategoryId, token);
_snackbar.Add("حذف برند با موفقیت انجام شد", Severity.Success);
}
catch (ApiException ex)
{
var exe = await ex.GetContentAsAsync<ApiResult>();
_snackbar.Add(exe != null ? exe.Message : ex.Content, Severity.Error);
}
catch (Exception e)
{
_snackbar.Add(e.Message, Severity.Error);
}
finally
{
IsProcessing = false;
}
}
}
}

View File

@ -1,5 +1,4 @@
@page "/CategoriesPage" @page "/CategoriesPage"
@using NetinaShop.AdminPanel.PWA.Utilities
@inject IDialogService DialogService @inject IDialogService DialogService
@inject NavigationManager NavigationManager @inject NavigationManager NavigationManager
@inject IRestWrapper RestWrapper @inject IRestWrapper RestWrapper

View File

@ -1,14 +1,4 @@
using Microsoft.AspNetCore.Components; namespace NetinaShop.AdminPanel.PWA.Pages;
using Microsoft.JSInterop;
using MudBlazor;
using NetinaShop.AdminPanel.PWA.Dialogs;
using NetinaShop.AdminPanel.PWA.Dialogs.Originals;
using NetinaShop.AdminPanel.PWA.Models.Api;
using NetinaShop.AdminPanel.PWA.Services.RestServices;
using NetinaShop.AdminPanel.PWA.Utilities;
using NetinaShop.Domain.Entities.ProductCategories;
namespace NetinaShop.AdminPanel.PWA.Pages;
public class CategoriesPageViewModel : BaseViewModel<List<ProductCategorySDto>> public class CategoriesPageViewModel : BaseViewModel<List<ProductCategorySDto>>
{ {

View File

@ -1,4 +1,6 @@
@page "/HomePage" 
@page "/"
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
<MudStack class="w-screen h-screen bg-[--color-background]"> <MudStack class="w-screen h-screen bg-[--color-background]">
<MudGrid> <MudGrid>

View File

@ -0,0 +1,6 @@
@page "/LoginPage"
<h3>LoginPage</h3>
@code {
}

View File

@ -1,5 +1,6 @@
@page "/ProductsPage" @page "/ProductsPage"
@using NetinaShop.AdminPanel.PWA.Dialogs @attribute [Microsoft.AspNetCore.Authorization.Authorize]
@inject IDialogService DialogService @inject IDialogService DialogService
<MudStack class="w-full p-8 h-screen bg-[--color-background]"> <MudStack class="w-full p-8 h-screen bg-[--color-background]">

View File

@ -1,9 +1,11 @@
using Blazored.LocalStorage; using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MudBlazor; using MudBlazor;
using MudBlazor.Services; using MudBlazor.Services;
using NetinaShop.AdminPanel.PWA; using NetinaShop.AdminPanel.PWA;
using NetinaShop.AdminPanel.PWA.Services;
using NetinaShop.AdminPanel.PWA.Services.RestServices; using NetinaShop.AdminPanel.PWA.Services.RestServices;
using NetinaShop.AdminPanel.PWA.Utilities; using NetinaShop.AdminPanel.PWA.Utilities;
using Toolbelt.Blazor.Extensions.DependencyInjection; using Toolbelt.Blazor.Extensions.DependencyInjection;
@ -11,6 +13,12 @@ using Toolbelt.Blazor.Extensions.DependencyInjection;
var builder = WebAssemblyHostBuilder.CreateDefault(args); var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app"); builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after"); builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddOptions();
builder.Services.AddAuthorizationCore();
builder.Services.AddApiAuthorization();
builder.Services.AddScoped<AuthenticationStateProvider,
CustomAuthenticationStateProvider>();
builder.Services.AddMudServices(config => builder.Services.AddMudServices(config =>
{ {
config.SnackbarConfiguration.VisibleStateDuration = 3500; config.SnackbarConfiguration.VisibleStateDuration = 3500;

View File

@ -0,0 +1,32 @@
using System.Security.Claims;
using Blazorise.Extensions;
using Microsoft.AspNetCore.Components.Authorization;
namespace NetinaShop.AdminPanel.PWA.Services;
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly IUserUtility _userUtility;
public CustomAuthenticationStateProvider(IUserUtility userUtility)
{
_userUtility = userUtility;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await _userUtility.GetBearerTokenAsync();
if (token.IsNullOrEmpty())
{
return new AuthenticationState(new());
}
var identity = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.Name, "mrfibuli"),
}, "Custom Authentication");
var user = new ClaimsPrincipal(identity);
return new AuthenticationState(user);
}
}

View File

@ -5,7 +5,7 @@ namespace NetinaShop.AdminPanel.PWA.Services.RestServices;
public interface ICrudApiRest<T, in TKey> where T : class public interface ICrudApiRest<T, in TKey> where T : class
{ {
[Post("")] [Post("")]
Task Create([Body] T payload, [Header("Authorization")] string authorization); Task Create<TCreateCommand>([Body] TCreateCommand payload, [Header("Authorization")] string authorization);
[Get("")] [Get("")]
Task<List<T>> ReadAll([Query] int page,[Header("Authorization")] string authorization); Task<List<T>> ReadAll([Query] int page,[Header("Authorization")] string authorization);
@ -28,7 +28,7 @@ public interface ICrudDtoApiRest<T, TDto, in TKey> where T : class where TDto :
Task Create([Body] TDto payload, [Header("Authorization")] string authorization); Task Create([Body] TDto payload, [Header("Authorization")] string authorization);
[Get("")] [Get("")]
Task<List<TDto>> ReadAll([Query]int page,[Header("Authorization")] string authorization); Task<List<TDto>> ReadAll([Query]int page);
[Get("")] [Get("")]
Task<List<TDto>> ReadAll(); Task<List<TDto>> ReadAll();

View File

@ -21,3 +21,6 @@
@using Refit @using Refit
@using NetinaShop.Domain.Entities.ProductCategories @using NetinaShop.Domain.Entities.ProductCategories
@using NetinaShop.AdminPanel.PWA.Services.RestServices @using NetinaShop.AdminPanel.PWA.Services.RestServices
@using Blazorise.Extensions
@using NetinaShop.AdminPanel.PWA.Utilities
@using Microsoft.AspNetCore.Components.Authorization

View File

@ -598,6 +598,10 @@ video {
margin-right: 0.5rem; margin-right: 0.5rem;
} }
.mt-3 {
margin-top: 0.75rem;
}
.mt-4 { .mt-4 {
margin-top: 1rem; margin-top: 1rem;
} }
@ -606,10 +610,6 @@ video {
margin-top: 1.25rem; margin-top: 1.25rem;
} }
.mt-3 {
margin-top: 0.75rem;
}
.flex { .flex {
display: flex; display: flex;
} }