Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add sales module sections and pages to Boilerplate (#9678) #9706

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
@attribute [Route(Urls.HomePage)]
@*+:cnd:noEmit*@
@attribute [Route(Urls.HomePage)]
@attribute [Route("{culture:nonfile?}" + Urls.HomePage)]
@inherits AppPageBase

<PageTitle>@Localizer[nameof(AppStrings.HomePageTitle)]</PageTitle>

<section>
<BitStack Alignment="BitAlignment.Center" Gap="2rem" Class="stack">
<BitStack Alignment="BitAlignment.Center" Gap="2rem" Class="root-stack">
<BitText Typography="BitTypography.H4" Align="BitTextAlign.Center">
@Localizer[nameof(AppStrings.HomePanelTitle)]
<br />
Expand All @@ -18,6 +19,50 @@

<br />

@*#if (module == "Sales")*@
@if (products is not null)
{
<BitCard FullWidth>
<BitCarousel Style="height:350px" AutoPlay AutoPlayInterval="5000" HideNextPrev InfiniteScrolling>
@foreach (var product in products)
{
<BitCarouselItem>
<BitStack Horizontal Class="carousel-stack">
<BitImage Src="@GetProductImageUrl(product)" Width="50%" />
<BitStack AutoHeight>
<BitText Typography="BitTypography.H1">@product.Name</BitText>
<BitText Typography="BitTypography.H6" Class="carousel-desc">@product.Description</BitText>
</BitStack>
</BitStack>
</BitCarouselItem>
}
</BitCarousel>
</BitCard>
}

<br />

<BitInfiniteScrolling ItemsProvider="LoadProducts" Class="products-inf-scr" ScrollerSelector="body">
<ItemTemplate Context="product">
<BitCard Background="BitColorKind.Tertiary" Class="product-item">
<BitStack Horizontal Class="product-stack">
<BitImage Src="@GetProductImageUrl(product)" Width="75px" />
<BitStack AutoHeight>
<BitText>@product.Name</BitText>
<BitText Typography="BitTypography.Body2" Class="product-desc">@product.Description</BitText>
</BitStack>
</BitStack>
</BitCard>
</ItemTemplate>
<LoadingTemplate>
<BitStack Alignment="BitAlignment.Center">
<BitEllipsisLoading />
</BitStack>
</LoadingTemplate>
</BitInfiniteScrolling>
@*#endif*@

@*#if (module == "Admin")*@
<CascadingValue Value="BitDir.Ltr">
<BitCard FullWidth Style="padding:2rem">
<BitStack>
Expand Down Expand Up @@ -112,7 +157,7 @@
</BitStack>
</BitCard>

<BitStack Horizontal Gap="2rem" Class="products-stack">
<BitStack Horizontal Gap="2rem" Class="bit-products-stack">
<BitCard Style="padding:3rem" FullWidth>
<BitStack HorizontalAlign="BitAlignment.Center">
<BitText Typography="BitTypography.H5">bit BlazorUI</BitText>
Expand Down Expand Up @@ -149,5 +194,6 @@
</BitStack>
</BitCard>
</BitStack>
@*#endif*@
</BitStack>
</section>
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using Boilerplate.Shared.Controllers.Statistics;
//+:cnd:noEmit
using Boilerplate.Shared.Controllers.Products;
using Boilerplate.Shared.Controllers.Statistics;
using Boilerplate.Shared.Dtos.Products;
using Boilerplate.Shared.Dtos.Statistics;

namespace Boilerplate.Client.Core.Components.Pages;
Expand All @@ -10,17 +13,24 @@ public partial class HomePage

[CascadingParameter] private BitDir? currentDir { get; set; }

//#if(module == "Admin")
[AutoInject] private IStatisticsController statisticsController = default!;

private bool isLoadingGitHub = true;
private bool isLoadingNuget = true;
private GitHubStats? gitHubStats;
private NugetStatsDto? nugetStats;
//#endif

//#if(module == "Sales")
[AutoInject] private IProductController productController = default!;
private IEnumerable<ProductDto>? products;
//#endif

protected override async Task OnInitAsync()
{
await base.OnInitAsync();

//#if(module == "Admin")
// If required, you should typically manage the authorization header for external APIs in **AuthDelegatingHandler.cs**
// and handle error extraction from failed responses in **ExceptionDelegatingHandler.cs**.

Expand All @@ -31,8 +41,16 @@ protected override async Task OnInitAsync()
// effectively addresses most scenarios.

await Task.WhenAll(LoadNuget(), LoadGitHub());
//#endif

//#if(module == "Sales")
products = (await PrerenderStateService.GetValue(() => HttpClient.GetFromJsonAsync("api/Product/GetHomeCarouselProducts",
JsonSerializerOptions.GetTypeInfo<List<ProductDto>>(),
CurrentCancellationToken)))!;
//#endif
}

//#if(module == "Admin")
private async Task LoadNuget()
{
try
Expand Down Expand Up @@ -68,4 +86,20 @@ private async Task LoadGitHub()
await InvokeAsync(StateHasChanged);
}
}
//#endif

//#if(module == "Sales")
private async ValueTask<IEnumerable<ProductDto>> LoadProducts(BitInfiniteScrollingItemsProviderRequest request)
{
await Task.Delay(2000);
return await productController.GetHomeProducts(request.Skip, 10, CurrentCancellationToken);
}
msynk marked this conversation as resolved.
Show resolved Hide resolved

private string GetProductImageUrl(ProductDto product)
{
return product.ImageFileName is null
? "_content/Boilerplate.Client.Core/images/product-placeholder.png"
: new Uri(AbsoluteServerAddress, $"/api/Attachment/GetProductImage/{product.Id}?v={product.ConcurrencyStamp}").ToString();
}
//#endif
msynk marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
@import '../../Styles/abstracts/_media-queries.scss';
//+:cnd:noEmit
@import '../../Styles/abstracts/_media-queries.scss';

section {
width: 100%;
Expand All @@ -7,11 +8,56 @@ section {
}

::deep {
.stack {
.root-stack {
max-width: 60rem;
}
//#if(module == "Sales")

.products-stack {
.carousel-stack {
display: flex !important;

@include lt-sm {
flex-direction: column !important;

.carousel-desc {
overflow: hidden;
max-height: 60px;
}
}
}

.products-inf-scr {
gap: 1rem;
display: flex;
flex-wrap: wrap;
position: relative;
}

.product-item {
height: 230px;
overflow: hidden;
width: calc(25% - 0.75rem);

@include lt-md {
width: calc(33% - 0.67rem);
}

@include lt-sm {
width: calc(50% - 0.5rem);

.product-stack {
flex-flow: column !important;
}

.product-desc {
overflow: hidden;
max-height: 50px;
}
}
}
//#endif

.bit-products-stack {
@include lt-sm {
flex-wrap: wrap;
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,24 @@ private async Task PublishUserProfileUpdated(UserDto user, CancellationToken can
await appHubContext.Clients.Clients(userSessionIdsExceptCurrentUserSessionId).SendAsync(SignalREvents.PUBLISH_MESSAGE, SharedPubSubMessages.PROFILE_UPDATED, user, cancellationToken);
}
//#endif

//#if (module == "Sales")
[AllowAnonymous]
[HttpGet("{productId}")]
[ResponseCache(Duration = 7 * 24 * 3600, Location = ResponseCacheLocation.Any, VaryByQueryKeys = new string[] { "*" })]
public async Task<IActionResult> GetProductImage(Guid productId, CancellationToken cancellationToken)
{
var product = await DbContext.Products.FirstOrDefaultAsync(p => p.Id == productId, cancellationToken);

if (product?.ImageFileName is null)
throw new ResourceNotFoundException();

var filePath = $"{AppSettings.ProductImagesDir}{product.ImageFileName}";

if (await blobStorage.ExistsAsync(filePath, cancellationToken) is false)
return new EmptyResult();

return File(await blobStorage.OpenReadAsync(filePath, cancellationToken), "image/webp", enableRangeProcessing: true);
}
//#endif
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,19 @@ private async Task Validate(Product product, CancellationToken cancellationToken
throw new ResourceValidationException((nameof(ProductDto.Name), [Localizer[nameof(AppStrings.DuplicateProductName)]]));
}
//#endif

//#if(module == "Sales")
[AllowAnonymous, HttpGet]
public async Task<List<ProductDto>> GetHomeCarouselProducts(CancellationToken cancellationToken)
{
return await Get().Take(10).ToListAsync(cancellationToken);
}

[AllowAnonymous, HttpGet("{skip}/{take}")]
public async Task<List<ProductDto>> GetHomeProducts(int skip, int take, CancellationToken cancellationToken)
{
return await Get().Skip(skip).Take(take).ToListAsync(cancellationToken);
}
msynk marked this conversation as resolved.
Show resolved Hide resolved
//#endif
}

Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,22 @@ public void Configure(EntityTypeBuilder<Product> builder)
new() { Id = Guid.Parse("eb787e1a-7ba8-4708-924b-9f7964fa0f64"), Name = "GT-R", Price = 113540, Description = "Legendary supercar with AWD, 4 seats, a powerful V6 engine and the latest tech", CreatedOn = baseDate.AddDays(-25), CategoryId = Guid.Parse("582b8c19-0709-4dae-b7a6-fa0e704dad3c"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("362a6638-0031-485d-825f-e8aeae63a334"), Name = "Juke", Price = 28100, Description = "A new smart SUV", CreatedOn = baseDate.AddDays(-35), CategoryId = Guid.Parse("582b8c19-0709-4dae-b7a6-fa0e704dad3c"), ConcurrencyStamp = defaultConcurrencyStamp },

new() { Id = Guid.Parse("8629931e-e26e-4885-b561-e447197d4b69"), Name = "H247", Price = 54950, Description = "", CreatedOn = baseDate.AddDays(-10), CategoryId = Guid.Parse("6fae78f3-b067-40fb-a2d5-9c8dd5eb2e08"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("a1c1987d-ee6c-41ad-9647-18de4504303a"), Name = "V297", Price = 103360, Description = "", CreatedOn = baseDate.AddDays(-15), CategoryId = Guid.Parse("6fae78f3-b067-40fb-a2d5-9c8dd5eb2e08"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("59eea437-bdf2-4c11-b262-06643b253288"), Name = "R50", Price = 2000000, Description = "", CreatedOn = baseDate.AddDays(-35), CategoryId = Guid.Parse("6fae78f3-b067-40fb-a2d5-9c8dd5eb2e08"), ConcurrencyStamp = defaultConcurrencyStamp },

new() { Id = Guid.Parse("01d223a3-182d-406a-9722-19dab083f96e"), Name = "M550i", Price = 77790, Description = "", CreatedOn = baseDate.AddDays(-10), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("64a2616f-3af6-4248-86cf-4a605095a644"), Name = "540i", Price = 60945, Description = "", CreatedOn = baseDate.AddDays(-15), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("ac50dc29-4b7e-4d4d-b23a-4227d91f2bb0"), Name = "530e", Price = 56545, Description = "", CreatedOn = baseDate.AddDays(-20), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("fb41cc51-9abd-4b45-b0d9-ea8f565ec502"), Name = "530i", Price = 55195, Description = "", CreatedOn = baseDate.AddDays(-25), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("e159b1ad-12aa-4e02-a39b-d5e4a32eaf99"), Name = "M850i", Price = 100045, Description = "", CreatedOn = baseDate.AddDays(-30), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("4d9cb0f4-1f32-45d5-8c84-d7f15bc569d5"), Name = "X7", Price = 77980, Description = "", CreatedOn = baseDate.AddDays(-35), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("1b22319e-0a58-471e-82b6-75cd8b9d98e1"), Name = "IX", Price = 87000, Description = "", CreatedOn = baseDate.AddDays(-40), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },

new() { Id = Guid.Parse("96c73b9c-03df-4f70-ac8d-75c32b89881a"), Name = "Model 3", Price = 61990, Description = "rapid acceleration and dynamic handling", CreatedOn = baseDate.AddDays(-10), CategoryId = Guid.Parse("747f6d66-7524-40ca-8494-f65e85b5ee5d"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("840ba759-bde9-4821-b49b-c981c082bb96"), Name = "Model S", Price = 135000, Description = "finishes near the top of our luxury electric car rankings.", CreatedOn = baseDate.AddDays(-15), CategoryId = Guid.Parse("747f6d66-7524-40ca-8494-f65e85b5ee5d"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("8629931e-e26e-4885-b561-e447197d4b69"), Name = "H247", Price = 54950, Description = "Subcompact luxury crossover SUV", CreatedOn = baseDate.AddDays(-10), CategoryId = Guid.Parse("6fae78f3-b067-40fb-a2d5-9c8dd5eb2e08"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("a1c1987d-ee6c-41ad-9647-18de4504303a"), Name = "V297", Price = 103360, Description = "Battery-electric full-size luxury liftback", CreatedOn = baseDate.AddDays(-15), CategoryId = Guid.Parse("6fae78f3-b067-40fb-a2d5-9c8dd5eb2e08"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("59eea437-bdf2-4c11-b262-06643b253288"), Name = "R50", Price = 2000000, Description = "Ultra-rare and powerful sports car", CreatedOn = baseDate.AddDays(-35), CategoryId = Guid.Parse("6fae78f3-b067-40fb-a2d5-9c8dd5eb2e08"), ConcurrencyStamp = defaultConcurrencyStamp },

new() { Id = Guid.Parse("01d223a3-182d-406a-9722-19dab083f96e"), Name = "M550i", Price = 77790, Description = "A powerful, sporty variant of the BMW 5 Series", CreatedOn = baseDate.AddDays(-10), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("64a2616f-3af6-4248-86cf-4a605095a644"), Name = "540i", Price = 60945, Description = "Luxurious and powerful sedan that combines elegant design with impressive performance", CreatedOn = baseDate.AddDays(-15), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("ac50dc29-4b7e-4d4d-b23a-4227d91f2bb0"), Name = "530e", Price = 56545, Description = "Combines class, spaciousness, and a well-built cabin", CreatedOn = baseDate.AddDays(-20), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("fb41cc51-9abd-4b45-b0d9-ea8f565ec502"), Name = "530i", Price = 55195, Description = "Zippy and fuel-efficient powertrain, and sure-footed handling", CreatedOn = baseDate.AddDays(-25), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("e159b1ad-12aa-4e02-a39b-d5e4a32eaf99"), Name = "M850i", Price = 100045, Description = "A Beastly coupe, powered by a fine-tuned 523-horsepower V8 engine", CreatedOn = baseDate.AddDays(-30), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("4d9cb0f4-1f32-45d5-8c84-d7f15bc569d5"), Name = "X7", Price = 77980, Description = "A full-size luxury crossover SUV that combines innovative design, an expansive presence, and a range of powerful engines", CreatedOn = baseDate.AddDays(-35), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("1b22319e-0a58-471e-82b6-75cd8b9d98e1"), Name = "IX", Price = 87000, Description = "Luxury crossover SUV that combines cutting-edge technology", CreatedOn = baseDate.AddDays(-40), CategoryId = Guid.Parse("ecf0496f-f1e3-4d92-8fe4-0d7fa2b4ffa4"), ConcurrencyStamp = defaultConcurrencyStamp },

new() { Id = Guid.Parse("96c73b9c-03df-4f70-ac8d-75c32b89881a"), Name = "Model 3", Price = 61990, Description = "Rapid acceleration and dynamic handling", CreatedOn = baseDate.AddDays(-10), CategoryId = Guid.Parse("747f6d66-7524-40ca-8494-f65e85b5ee5d"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("840ba759-bde9-4821-b49b-c981c082bb96"), Name = "Model S", Price = 135000, Description = "Finishes near the top of our luxury electric car rankings.", CreatedOn = baseDate.AddDays(-15), CategoryId = Guid.Parse("747f6d66-7524-40ca-8494-f65e85b5ee5d"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("840e113b-5074-4b1c-86bd-e9affb659412"), Name = "Model X", Price = 138890, Description = "Heart-pumping acceleration, long drive range", CreatedOn = baseDate.AddDays(-20), CategoryId = Guid.Parse("747f6d66-7524-40ca-8494-f65e85b5ee5d"), ConcurrencyStamp = defaultConcurrencyStamp },
new() { Id = Guid.Parse("b2db9074-a0a9-4054-87e2-206b7a55c793"), Name = "Model Y", Price = 67790, Description = "extensive driving range, lots of standard safety features", CreatedOn = baseDate.AddDays(-35), CategoryId = Guid.Parse("747f6d66-7524-40ca-8494-f65e85b5ee5d"), ConcurrencyStamp = defaultConcurrencyStamp }
new() { Id = Guid.Parse("b2db9074-a0a9-4054-87e2-206b7a55c793"), Name = "Model Y", Price = 67790, Description = "Extensive driving range, lots of standard safety features", CreatedOn = baseDate.AddDays(-35), CategoryId = Guid.Parse("747f6d66-7524-40ca-8494-f65e85b5ee5d"), ConcurrencyStamp = defaultConcurrencyStamp }
);
}
}
Expand Down
Loading
Loading