Implemented basic store and store admin system. Added alerts. Added db models for store
This commit is contained in:
27
Moonlight/Shared/Components/Forms/SmartEnumSelect.razor
Normal file
27
Moonlight/Shared/Components/Forms/SmartEnumSelect.razor
Normal file
@@ -0,0 +1,27 @@
|
||||
@typeparam TField where TField : struct
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@inherits InputBase<TField>
|
||||
|
||||
<select @bind="CurrentValue" class="form-select">
|
||||
@foreach (var status in (TField[])Enum.GetValues(typeof(TField)))
|
||||
{
|
||||
if (CurrentValue.ToString() == status.ToString())
|
||||
{
|
||||
<option value="@(status)" selected="">@(status)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(status)">@(status)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
|
||||
@code
|
||||
{
|
||||
protected override bool TryParseValueFromString(string? value, out TField result, out string? validationErrorMessage)
|
||||
{
|
||||
result = Enum.Parse<TField>(value);
|
||||
validationErrorMessage = "";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
71
Moonlight/Shared/Components/Forms/SmartSelect.razor
Normal file
71
Moonlight/Shared/Components/Forms/SmartSelect.razor
Normal file
@@ -0,0 +1,71 @@
|
||||
@typeparam TField
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@inherits InputBase<TField>
|
||||
|
||||
<select class="form-select" @bind="Binding">
|
||||
@if (CanBeNull)
|
||||
{
|
||||
<option value="-1">---</option>
|
||||
}
|
||||
@foreach (var item in Items)
|
||||
{
|
||||
<option value="@(item!.GetHashCode())">@(DisplayField(item))</option>
|
||||
}
|
||||
</select>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public IEnumerable<TField> Items { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<TField, string> DisplayField { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnChange { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool CanBeNull { get; set; } = false;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override string? FormatValueAsString(TField? value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
return DisplayField.Invoke(value);
|
||||
}
|
||||
|
||||
protected override bool TryParseValueFromString(string? value, out TField result, out string? validationErrorMessage)
|
||||
{
|
||||
validationErrorMessage = "";
|
||||
result = default(TField)!;
|
||||
return false;
|
||||
}
|
||||
|
||||
private int Binding
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Value == null)
|
||||
return -1;
|
||||
|
||||
return Value.GetHashCode();
|
||||
}
|
||||
set
|
||||
{
|
||||
var i = Items.FirstOrDefault(x => x!.GetHashCode() == value);
|
||||
|
||||
if(i == null && !CanBeNull)
|
||||
return;
|
||||
|
||||
Value = i;
|
||||
ValueChanged.InvokeAsync(i);
|
||||
OnChange?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Moonlight/Shared/Components/Partials/SmartModal.razor
Normal file
35
Moonlight/Shared/Components/Partials/SmartModal.razor
Normal file
@@ -0,0 +1,35 @@
|
||||
@inject ModalService ModalService
|
||||
|
||||
<div class="modal fade" id="modal@(Id)" tabindex="-1">
|
||||
<div class="modal-dialog @(CssClasses)">
|
||||
<div class="modal-content">
|
||||
@ChildContent
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public string CssClasses { get; set; } = "";
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
private int Id;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Id = GetHashCode();
|
||||
}
|
||||
|
||||
public async Task Show()
|
||||
{
|
||||
await ModalService.Show("modal" + Id);
|
||||
}
|
||||
|
||||
public async Task Hide()
|
||||
{
|
||||
await ModalService.Hide("modal" + Id);
|
||||
}
|
||||
}
|
||||
316
Moonlight/Shared/Components/Store/StoreModals.razor
Normal file
316
Moonlight/Shared/Components/Store/StoreModals.razor
Normal file
@@ -0,0 +1,316 @@
|
||||
@using Moonlight.App.Services.Store
|
||||
@using Moonlight.App.Models.Forms.Store
|
||||
@using Moonlight.App.Database.Enums
|
||||
@using Moonlight.App.Database.Entities.Store
|
||||
@using Moonlight.App.Repositories
|
||||
@using Mappy.Net
|
||||
|
||||
@inject StoreService StoreService
|
||||
@inject ToastService ToastService
|
||||
@inject Repository<Category> CategoryRepository
|
||||
|
||||
<SmartModal @ref="AddCategoryModal" CssClasses="modal-dialog-centered">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add new category</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<SmartForm Model="AddCategoryForm" OnValidSubmit="AddCategorySubmit">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input @bind="AddCategoryForm.Name" class="form-control" type="text"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea @bind="AddCategoryForm.Description" class="form-control" type="text"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Slug</label>
|
||||
<input @bind="AddCategoryForm.Slug" class="form-control" type="text"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</SmartModal>
|
||||
|
||||
<SmartModal @ref="EditCategoryModal" CssClasses="modal-dialog-centered">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit category</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<SmartForm Model="EditCategoryForm" OnValidSubmit="EditCategorySubmit">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input @bind="EditCategoryForm.Name" class="form-control" type="text"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea @bind="EditCategoryForm.Description" class="form-control" type="text"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Slug</label>
|
||||
<input @bind="EditCategoryForm.Slug" class="form-control" type="text"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</SmartModal>
|
||||
|
||||
<SmartModal @ref="AddProductModal" CssClasses="modal-dialog-centered modal-lg">
|
||||
<LazyLoader Load="LoadCategories">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Add new product</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<SmartForm Model="AddProductForm" OnValidSubmit="AddProductSubmit">
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input @bind="AddProductForm.Name" class="form-control" type="text"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea @bind="AddProductForm.Description" class="form-control" type="text"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Slug</label>
|
||||
<input @bind="AddProductForm.Slug" class="form-control" type="text"/>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<label class="form-label">Category</label>
|
||||
<SmartSelect @bind-Value="AddProductForm.Category" Items="Categories" DisplayField="@(x => x.Name)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Price</label>
|
||||
<input @bind="AddProductForm.Price" class="form-control" type="text"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Duration</label>
|
||||
<input @bind="AddProductForm.Duration" class="form-control" type="number"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Max instances per user</label>
|
||||
<input @bind="AddProductForm.MaxPerUser" class="form-control" type="number"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Stock</label>
|
||||
<input @bind="AddProductForm.Stock" class="form-control" type="number"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Type</label>
|
||||
<SmartEnumSelect @bind-Value="AddProductForm.Type"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Config</label>
|
||||
<input @bind="AddProductForm.ConfigJson" class="form-control" type="text"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</LazyLoader>
|
||||
</SmartModal>
|
||||
|
||||
<SmartModal @ref="EditProductModal" CssClasses="modal-dialog-centered modal-lg">
|
||||
<LazyLoader Load="LoadCategories">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Edit product</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<SmartForm Model="EditProductForm" OnValidSubmit="EditProductSubmit">
|
||||
<div class="modal-body">
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-12">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Name</label>
|
||||
<input @bind="EditProductForm.Name" class="form-control" type="text"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description</label>
|
||||
<textarea @bind="EditProductForm.Description" class="form-control" type="text"></textarea>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Slug</label>
|
||||
<input @bind="EditProductForm.Slug" class="form-control" type="text"/>
|
||||
</div>
|
||||
<div class="mb-5">
|
||||
<label class="form-label">Category</label>
|
||||
<SmartSelect @bind-Value="EditProductForm.Category" Items="Categories" DisplayField="@(x => x.Name)"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-12">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Price</label>
|
||||
<input @bind="EditProductForm.Price" class="form-control" type="text"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Duration</label>
|
||||
<input @bind="EditProductForm.Duration" class="form-control" type="number"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Max instances per user</label>
|
||||
<input @bind="EditProductForm.MaxPerUser" class="form-control" type="number"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Stock</label>
|
||||
<input @bind="EditProductForm.Stock" class="form-control" type="number"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Type</label>
|
||||
<SmartEnumSelect @bind-Value="EditProductForm.Type"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Config</label>
|
||||
<input @bind="EditProductForm.ConfigJson" class="form-control" type="text"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</LazyLoader>
|
||||
</SmartModal>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public Func<Task> OnUpdate { get; set; }
|
||||
|
||||
#region Add category
|
||||
|
||||
private SmartModal AddCategoryModal;
|
||||
private AddCategoryForm AddCategoryForm = new();
|
||||
|
||||
public Task AddCategoryShow => AddCategoryModal.Show();
|
||||
|
||||
private async Task AddCategorySubmit()
|
||||
{
|
||||
await StoreService.Admin.AddCategory(
|
||||
AddCategoryForm.Name,
|
||||
AddCategoryForm.Description,
|
||||
AddCategoryForm.Slug
|
||||
);
|
||||
|
||||
await ToastService.Success("Successfully added category");
|
||||
await AddCategoryModal.Hide();
|
||||
|
||||
AddCategoryForm = new();
|
||||
await OnUpdate.Invoke();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Edit category
|
||||
|
||||
private SmartModal EditCategoryModal;
|
||||
private EditCategoryForm EditCategoryForm = new();
|
||||
private Category EditCategory;
|
||||
|
||||
public async Task EditCategoryShow(Category category)
|
||||
{
|
||||
EditCategory = category;
|
||||
|
||||
EditCategoryForm = Mapper.Map<EditCategoryForm>(EditCategory);
|
||||
await EditCategoryModal.Show();
|
||||
}
|
||||
private async Task EditCategorySubmit()
|
||||
{
|
||||
EditCategory = Mapper.Map(EditCategory, EditCategoryForm);
|
||||
|
||||
await StoreService.Admin.UpdateCategory(EditCategory);
|
||||
|
||||
await ToastService.Success("Successfully updated category");
|
||||
await EditCategoryModal.Hide();
|
||||
|
||||
await OnUpdate.Invoke();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Add product
|
||||
|
||||
private SmartModal AddProductModal;
|
||||
private AddProductForm AddProductForm = new();
|
||||
private Category[] Categories;
|
||||
|
||||
public Task AddProductShow => AddProductModal.Show();
|
||||
|
||||
private async Task AddProductSubmit()
|
||||
{
|
||||
await StoreService.Admin.AddProduct(
|
||||
AddProductForm.Name,
|
||||
AddProductForm.Description,
|
||||
AddProductForm.Slug,
|
||||
AddProductForm.Type,
|
||||
AddProductForm.ConfigJson,
|
||||
product =>
|
||||
{
|
||||
product.Category = AddProductForm.Category;
|
||||
product.Duration = AddProductForm.Duration;
|
||||
product.Price = AddProductForm.Price;
|
||||
product.Stock = AddProductForm.Stock;
|
||||
product.MaxPerUser = AddProductForm.MaxPerUser;
|
||||
}
|
||||
);
|
||||
|
||||
await ToastService.Success("Successfully added product");
|
||||
await AddProductModal.Hide();
|
||||
|
||||
AddProductForm = new();
|
||||
await OnUpdate.Invoke();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Edit product
|
||||
|
||||
private SmartModal EditProductModal;
|
||||
private EditProductForm EditProductForm = new();
|
||||
private Product EditProduct;
|
||||
|
||||
public async Task EditProductShow(Product product)
|
||||
{
|
||||
EditProduct = product;
|
||||
|
||||
EditProductForm = Mapper.Map<EditProductForm>(EditProduct);
|
||||
await EditProductModal.Show();
|
||||
}
|
||||
|
||||
private async Task EditProductSubmit()
|
||||
{
|
||||
EditProduct = Mapper.Map(EditProduct, EditProductForm);
|
||||
|
||||
await StoreService.Admin.UpdateProduct(EditProduct);
|
||||
|
||||
await ToastService.Success("Successfully updated product");
|
||||
await EditProductModal.Hide();
|
||||
|
||||
await OnUpdate.Invoke();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Task LoadCategories(LazyLoader _)
|
||||
{
|
||||
Categories = CategoryRepository.Get().ToArray();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
263
Moonlight/Shared/Views/Store/Index.razor
Normal file
263
Moonlight/Shared/Views/Store/Index.razor
Normal file
@@ -0,0 +1,263 @@
|
||||
@page "/store"
|
||||
|
||||
@using Moonlight.App.Database.Entities.Store
|
||||
@using Moonlight.App.Models.Enums
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Store
|
||||
@using Moonlight.Shared.Components.Store
|
||||
|
||||
@inject Repository<Category> CategoryRepository
|
||||
@inject Repository<Product> ProductRepository
|
||||
@inject ConfigService ConfigService
|
||||
@inject IdentityService IdentityService
|
||||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
@inject StoreService StoreService
|
||||
|
||||
@{
|
||||
var currency = ConfigService.Get().Store.Currency;
|
||||
}
|
||||
|
||||
@if (IdentityService.Permissions[Permission.AdminStore])
|
||||
{
|
||||
<div class="alert alert-info bg-info text-white text-center py-2">
|
||||
@if (EditMode)
|
||||
{
|
||||
<h4 class="pt-2">Edit mode enabled. Disable it by clicking <a href="#" @onclick="ToggleEdit" @onclick:preventDefault>here</a></h4>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h4 class="pt-2">To edit the store you can enable the edit mode <a href="#" @onclick="ToggleEdit" @onclick:preventDefault>here</a></h4>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-12 mb-5">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6 class="card-title">Categories</h6>
|
||||
@if (EditMode)
|
||||
{
|
||||
<div class="card-toolbar">
|
||||
<button @onclick="() => StoreModals.AddCategoryShow" class="btn btn-icon btn-success">
|
||||
<i class="bx bx-sm bx-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<LazyLoader @ref="CategoriesLazyLoader" Load="LoadCategories">
|
||||
@foreach (var category in Categories)
|
||||
{
|
||||
<div class="d-flex flex-column">
|
||||
<li class="d-flex align-items-center py-2">
|
||||
<span class="bullet me-5"></span>
|
||||
<a class="invisible-a fs-5 @(SelectedCategory == category ? "fw-bold text-primary" : "")" href="/store?category=@(category.Slug)">@(category.Name)</a>
|
||||
@if (EditMode)
|
||||
{
|
||||
<a @onclick="() => StoreModals.EditCategoryShow(category)" @onclick:preventDefault href="#" class="ms-3 text-warning">Edit</a>
|
||||
<a @onclick="() => DeleteCategory(category)" @onclick:preventDefault href="#" class="ms-1 text-danger">Delete</a>
|
||||
}
|
||||
</li>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9 col-12">
|
||||
<LazyLoader @ref="ProductsLazyLoader" Load="LoadProducts">
|
||||
@if (Products.Any())
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var product in Products)
|
||||
{
|
||||
<div class="col-md-4 col-12 mb-5">
|
||||
<div class="card">
|
||||
@if (EditMode)
|
||||
{
|
||||
<div class="card-header">
|
||||
<a @onclick="() => StoreModals.EditProductShow(product)" @onclick:preventDefault href="#" class="card-title text-primary">Edit</a>
|
||||
<div class="card-toolbar">
|
||||
<a @onclick="() => DeleteProduct(product)" @onclick:preventDefault href="#" class="text-danger">Delete</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="card-body text-center">
|
||||
<h1 class="text-dark mb-5 fw-bolder">@(product.Name)</h1>
|
||||
<p class="fw-semibold fs-6 text-gray-800 flex-grow-1">
|
||||
@(Formatter.FormatLineBreaks(product.Description))
|
||||
</p>
|
||||
|
||||
<div class="text-center mb-8">
|
||||
@if (product.Price == 0)
|
||||
{
|
||||
<span class="fs-1 fw-bold text-primary">
|
||||
Free
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="mb-2 text-primary">@(currency)</span>
|
||||
<span class="fs-1 fw-bold text-primary">
|
||||
@(product.Price)
|
||||
</span>
|
||||
<span class="fs-7 fw-semibold opacity-50">
|
||||
/
|
||||
<span>@(product.Duration) days</span>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (product.Stock == 0)
|
||||
{
|
||||
<button class="btn btn-primary disabled">Out of stock</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="/store/order/@(product.Slug)" class="btn btn-primary">Order now</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (EditMode)
|
||||
{
|
||||
<div class="col-md-4 col-12 mb-5">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<button @onclick="() => StoreModals.AddProductShow" class="btn btn-success">Create new product</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Categories.Any())
|
||||
{
|
||||
if (EditMode)
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-body text-center py-10">
|
||||
<button @onclick="() => StoreModals.AddProductShow" class="btn btn-success">Create new product</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card card-body text-center">
|
||||
<div class="py-10">
|
||||
<h1 class="card-title">Welcome to our store</h1>
|
||||
<span class="card-subtitle fs-2">Select a category to start browsing</span>
|
||||
</div>
|
||||
|
||||
<div class="py-10 text-center p-10">
|
||||
<img src="/svg/shopping.svg" style="height: 10vi" alt="Banner">
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card card-body text-center">
|
||||
<h1 class="card-title py-10">No products found</h1>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<StoreModals @ref="StoreModals" OnUpdate="OnParametersSetAsync" />
|
||||
|
||||
@code
|
||||
{
|
||||
// Category
|
||||
private Category[] Categories;
|
||||
private LazyLoader? CategoriesLazyLoader;
|
||||
|
||||
[Parameter]
|
||||
[SupplyParameterFromQuery]
|
||||
public string Category { get; set; }
|
||||
|
||||
private Category? SelectedCategory;
|
||||
|
||||
// Products
|
||||
private Product[] Products;
|
||||
private LazyLoader? ProductsLazyLoader;
|
||||
|
||||
// Edit stuff
|
||||
private bool EditMode = false;
|
||||
private StoreModals StoreModals;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
if (CategoriesLazyLoader != null)
|
||||
await CategoriesLazyLoader.Reload();
|
||||
|
||||
if (ProductsLazyLoader != null)
|
||||
await ProductsLazyLoader.Reload();
|
||||
}
|
||||
|
||||
private async Task ToggleEdit()
|
||||
{
|
||||
EditMode = !EditMode;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
private Task LoadCategories(LazyLoader _)
|
||||
{
|
||||
Categories = CategoryRepository.Get().ToArray();
|
||||
|
||||
SelectedCategory = Categories.FirstOrDefault(x => x.Slug == Category);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private Task LoadProducts(LazyLoader _)
|
||||
{
|
||||
if (SelectedCategory == null)
|
||||
{
|
||||
Products = ProductRepository
|
||||
.Get()
|
||||
.Where(x => x.Category == null)
|
||||
.ToArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
Products = ProductRepository
|
||||
.Get()
|
||||
.Where(x => x.Category!.Id == SelectedCategory.Id)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task DeleteCategory(Category category)
|
||||
{
|
||||
if (!await AlertService.YesNo($"Do you really want to delete '{category.Name}'", "Continue", "Cancel"))
|
||||
return;
|
||||
|
||||
await StoreService.Admin.DeleteCategory(category);
|
||||
|
||||
await ToastService.Success("Successfully deleted category");
|
||||
await OnParametersSetAsync();
|
||||
}
|
||||
|
||||
private async Task DeleteProduct(Product product)
|
||||
{
|
||||
if (!await AlertService.YesNo($"Do you really want to delete '{product.Name}'", "Continue", "Cancel"))
|
||||
return;
|
||||
|
||||
await StoreService.Admin.DeleteProduct(product);
|
||||
|
||||
await ToastService.Success("Successfully deleted product");
|
||||
await OnParametersSetAsync();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user