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">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
<FocusOnNavigate RouteData="@routeData" Selector="h1" />
<AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
<NotAuthorized>
<LoginPage/>
</NotAuthorized>
</AuthorizeRouteView>
<FocusOnNavigate RouteData="@routeData" Selector="h1"/>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
@ -9,4 +17,6 @@
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</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 IRestWrapper RestWrapper
@inject IUserUtility UserUtility
<MudDialog class="mx-auto">
<DialogContent>
@ -63,6 +66,32 @@
void Submit() => MudDialog.Close(DialogResult.Ok(true));
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 ProductCategorySDto? _selectedCategory;

View File

@ -22,47 +22,71 @@
color: #fff !important;
}
</style>
<MudRTLProvider RightToLeft="true">
<MudThemeProvider Theme="MyCustomTheme" />
<MudDialogProvider />
<MudSnackbarProvider />
<MudLayout>
<MudAppBar class="py-2" Color="Color.Transparent" Fixed="false" Elevation="2">
<MudHidden Breakpoint="Breakpoint.SmAndUp">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" />
</MudHidden>
<MudAvatar Size="Size.Large" Variant="Variant.Outlined">
<MudImage Src="https://img.freepik.com/free-photo/portrait-white-man-isolated_53876-40306.jpg?size=626&ext=jpg&ga=GA1.1.632798143.1705708800&semt=ais"></MudImage>
</MudAvatar>
<MudStack class="mr-2" Spacing="0">
<MudText Color="Color.Inherit" Typo="Typo.body1"><b>امیرحسین خادمی</b></MudText>
<MudText Color="Color.Inherit" Typo="Typo.caption">09214802813</MudText>
</MudStack>
<MudSpacer />
<MudIconButton Size="Size.Medium" Color="Color.Inherit" Icon="@Icons.Material.Outlined.Settings"/>
<MudIconButton Size="Size.Medium" Color="Color.Error" Icon="@Icons.Material.Outlined.ExitToApp" />
</MudAppBar>
<MudGrid Spacing="0">
<AuthorizeView>
<Authorized>
<MudRTLProvider RightToLeft="true">
<MudThemeProvider Theme="MyCustomTheme" />
<MudDialogProvider />
<MudSnackbarProvider />
<MudItem sm="0" md="3" lg="2">
<MudHidden Breakpoint="Breakpoint.SmAndDown">
<SideBarUi/>
</MudHidden>
</MudItem>
<MudItem sm="12" md="9" lg="10">
<MudLayout>
<MudAppBar class="py-2" Color="Color.Transparent" Fixed="false" Elevation="2">
<MudHidden Breakpoint="Breakpoint.SmAndUp">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" />
</MudHidden>
<MudAvatar Size="Size.Large" Variant="Variant.Outlined">
<MudImage Src="https://img.freepik.com/free-photo/portrait-white-man-isolated_53876-40306.jpg?size=626&ext=jpg&ga=GA1.1.632798143.1705708800&semt=ais"></MudImage>
</MudAvatar>
<MudStack class="mr-2" Spacing="0">
<MudText Color="Color.Inherit" Typo="Typo.body1"><b>امیرحسین خادمی</b></MudText>
<MudText Color="Color.Inherit" Typo="Typo.caption">09214802813</MudText>
</MudStack>
<MudSpacer />
<MudIconButton Size="Size.Medium" Color="Color.Inherit" Icon="@Icons.Material.Outlined.Settings"/>
<MudIconButton Size="Size.Medium" Color="Color.Error" Icon="@Icons.Material.Outlined.ExitToApp" />
</MudAppBar>
<MudGrid Spacing="0">
<MudItem sm="0" md="3" lg="2">
<MudHidden Breakpoint="Breakpoint.SmAndDown">
<SideBarUi/>
</MudHidden>
</MudItem>
<MudItem sm="12" md="9" lg="10">
<div>
@Body
<div dir="ltr">
<PWAUpdater Text="@_updateText" ButtonCaption="اپدیت کنید"/>
</div>
</div>
</MudItem>
</MudGrid>
</MudLayout>
</MudRTLProvider>
</Authorized>
<NotAuthorized>
<MudRTLProvider RightToLeft="true">
<MudThemeProvider Theme="MyCustomTheme" />
<MudDialogProvider />
<MudSnackbarProvider />
<MudLayout>
<div>
@Body
<div dir="ltr">
<PWAUpdater Text="@_updateText" ButtonCaption="اپدیت کنید"/>
</div>
</div>
</MudItem>
</MudGrid>
</MudLayout>
</MudRTLProvider>
</MudLayout>
</MudRTLProvider>
</NotAuthorized>
</AuthorizeView>
@code
{
MudTheme MyCustomTheme = new MudTheme()

View File

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

View File

@ -14,11 +14,13 @@
<ItemGroup>
<PackageReference Include="Blazored.LocalStorage" Version="4.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.WebAssembly.DevServer" Version="8.0.0" PrivateAssets="all" />
<PackageReference Include="MudBlazor" Version="6.11.2" />
<PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.1" />
<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="Radzen.Blazor" Version="4.24.1" />
<PackageReference Include="Radzen.Blazor" Version="4.24.2" />
<PackageReference Include="Refit" Version="7.0.0" />
<PackageReference Include="Refit.HttpClientFactory" Version="7.0.0" />
<PackageReference Include="Refit.Newtonsoft.Json" Version="7.0.0" />
@ -40,10 +42,19 @@
<ItemGroup>
<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.Api" />
<Using Include="NetinaShop.AdminPanel.PWA.Services.RestServices" />
<Using Include="NetinaShop.AdminPanel.PWA.Utilities" />
<Using Include="NetinaShop.Common.Models.Api" />
<Using Include="NetinaShop.Domain.Dtos.RequestDtos" />
<Using Include="NetinaShop.Domain.Dtos.SmallDtos" />
<Using Include="NetinaShop.Domain.Entities.ProductCategories" />
<Using Include="NetinaShop.Domain.Enums" />
<Using Include="Newtonsoft.Json" />
<Using Include="Refit" />

View File

@ -1,6 +1,80 @@
@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 {

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"
@using NetinaShop.AdminPanel.PWA.Utilities
@inject IDialogService DialogService
@inject NavigationManager NavigationManager
@inject IRestWrapper RestWrapper

View File

@ -1,14 +1,4 @@
using Microsoft.AspNetCore.Components;
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;
namespace NetinaShop.AdminPanel.PWA.Pages;
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]">
<MudGrid>

View File

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

View File

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

View File

@ -1,9 +1,11 @@
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using MudBlazor;
using MudBlazor.Services;
using NetinaShop.AdminPanel.PWA;
using NetinaShop.AdminPanel.PWA.Services;
using NetinaShop.AdminPanel.PWA.Services.RestServices;
using NetinaShop.AdminPanel.PWA.Utilities;
using Toolbelt.Blazor.Extensions.DependencyInjection;
@ -11,6 +13,12 @@ using Toolbelt.Blazor.Extensions.DependencyInjection;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
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 =>
{
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
{
[Post("")]
Task Create([Body] T payload, [Header("Authorization")] string authorization);
Task Create<TCreateCommand>([Body] TCreateCommand payload, [Header("Authorization")] string authorization);
[Get("")]
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);
[Get("")]
Task<List<TDto>> ReadAll([Query]int page,[Header("Authorization")] string authorization);
Task<List<TDto>> ReadAll([Query]int page);
[Get("")]
Task<List<TDto>> ReadAll();

View File

@ -21,3 +21,6 @@
@using Refit
@using NetinaShop.Domain.Entities.ProductCategories
@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;
}
.mt-3 {
margin-top: 0.75rem;
}
.mt-4 {
margin-top: 1rem;
}
@ -606,10 +610,6 @@ video {
margin-top: 1.25rem;
}
.mt-3 {
margin-top: 0.75rem;
}
.flex {
display: flex;
}